MHeironimus / ArduinoJoystickLibrary

An Arduino library that adds one or more joysticks to the list of HID devices an Arduino Leonardo or Arduino Micro can support.
GNU Lesser General Public License v3.0
2.1k stars 409 forks source link

Axis freeze on Windows cold boot #126

Open AfromD opened 4 years ago

AfromD commented 4 years ago

Description of Issue

When I plug the Arduino into a running computer, axis are updated according to the inputs. However, often when cold booting windows, the Arduino shows up as a joystick, but the axis are frozen. When debugging with the serial output active, I can see the script is running and updating variables, however, they do not seem to update the joystick axis in Windows. I can make it work again by momentarily unplugging the usb connection.

Technical Details

Sketch File that Reproduces Issue


/*            Joystick script with filtering
 *  This script takes two axis, then oversamples a 10 bit 
 *  input signal at a rate of 200Hz to get an 11 bit output 
 *  at 50 Hz. The clock is used to exactly time the polling, 
 *  to avoid sampling bias. A crude bandpass filter throws 
 *  away spikes that are impossible(!) to result from user 
 *  input. This will convert spikes on the control axis into 
 *  a mere stutter. Then a Kalman filter is applied to the 
 *  50 Hz 11 bit signal to remove remaining gaussian noise. 
 *  X and Y each get their own one dimensional Kalman 
 *  because there is no correlation that is relevant for the 
 *  filter. The constants in this script are configured for 
 *  the Saitek Pro yoke pitch and roll axis.
*/

#include "Joystick.h"
#include "SimpleKalmanFilter.h"

#define CENTER_X 1170
#define CENTER_Y 1000
#define RANGE 840
#define ADC_BITS 10      
#define OUTPUT_BITS 11   
#define OUTPUT_HZ 50
#define BANDPASS_SPD 10 
#define MEASUREM_UNCERT 5
#define PROC_VARIANCE 3

/*                      Settings
* CENTER_X           Center of the roll axis in output resolution
* CENTER_Y           Center of the pitch axis in output resolution
* RANGE              Range of movement in output resolution
* ADC_BITS           Resolution of the ADC on the board
* OUTPUT_BITS        Desired output resolution bitdepth. Setting this 
*                    higher than ADC_BITS will invoke oversampling
* OUTPUT_HZ          Desired output frequency to the computer
* BANDPASS_SPD       Rate of change over which measurements will be
*                    discarded. 10 Means 10 times the full range per 
*                    second. Don't set the bandpass speed too low, 
*                    erratic output if it is actually achieved! It 
*                    needs to be a value where with 100% certainty it 
*                    is a potentiometer spike and not user input!
* MEASUREMENT_UNCERT Random noise to be expected from the Pot,
*                    in output resolution units
* PROC_VARIANCE      Normal accelerations of the control to be 
*                    expected, in output resoltution units per sample 
*                    in OUTPUT_HZ. Decreasing this will decrease 
*                    responsiveness
*              
* Go easy on the oversampling, it has a exponential performance
* impact. 2 Bit oversampling will slow down the script 16 
* times compared to no oversampling! Depending on the board, the
* output frequency might need to be reduced for the processor to 
* not miss samples.
*/

int potX, valX;
int potY, valY;
int bandPass;
unsigned long sumX, sumY; 
unsigned long lastPoll = 0;
unsigned int oSampleBits, sampleTime, adcRes, samples;

// output just X and Y axis
Joystick_ Joystick(JOYSTICK_DEFAULT_REPORT_ID, 
  JOYSTICK_TYPE_JOYSTICK, 0, 0,
  true, true, false, false, false, false,
  false, false, false, false, false);

SimpleKalmanFilter kalmanFilterX(MEASUREM_UNCERT, MEASUREM_UNCERT, PROC_VARIANCE);
SimpleKalmanFilter kalmanFilterY(MEASUREM_UNCERT, MEASUREM_UNCERT, PROC_VARIANCE);

void setup() {
  Joystick.begin();
  Joystick.setXAxisRange(-RANGE, RANGE);
  Joystick.setYAxisRange(-RANGE, RANGE);

  #if defined(ARDUINO_ARCH_SAMD) || defined(__SAM3X8E__) || defined(ARDUINO_SAMD_MKRZERO)
    analogReadResolution(ADC_BITS);
  #endif

  pinMode(A0, INPUT);
  pinMode(A1, INPUT);

  //initialize parameters for the filters and sampling
  oSampleBits = constrain(OUTPUT_BITS - ADC_BITS, 0, 16-ADC_BITS);
  sampleTime = 1000000 / ( pow(4,oSampleBits) * OUTPUT_HZ );
  bandPass = pow(2,ADC_BITS);

  //Prepare some calculations for the loop
  adcRes = pow(2,ADC_BITS);
  samples = pow(4,oSampleBits);

  //Serial.begin( 9600 ); //remove comment for debugging on the serial monitor/plotter
}

void loop() {

  //enable band pass filter after 2 seconds stabilization
  if (bandPass ==  adcRes && millis() > 2000) {
    bandPass = (BANDPASS_SPD * adcRes) / OUTPUT_HZ;
  }

  //oversample start
  sumX = 0;
  sumY = 0;
  for(int i=0; i < samples; i++) { //sum up total of 4^oSampleBits measurements 

    while (lastPoll+sampleTime > micros()) {delayMicroseconds(4);} //clocked sample rate.
    potX = analogRead(A1); //Roll axis, adjust A1/A0 according to actual wiring
    potY = analogRead(A0); //Pitch axis
    lastPoll = micros();

    if (abs((valX >> oSampleBits)-potX) < bandPass) { //very crude bandpass filter
      sumX += potX;                     
      } else {
      sumX += valX >> oSampleBits; //use last value as a substitute, valX in ADC_BITS
      //Serial.println(" !!!BANDPASS FILTER HAS TWROWN OUT AN INPUT VALUE!!!");
    }
    if (abs((valY >> oSampleBits)-potY) < bandPass) {
      sumY += potY;
    } else {
      sumX += valY >> oSampleBits;
      //Serial.println("!!!BANDPASS FILTER HAS TWROWN OUT AN INPUT VALUE!!!");
    }

  }
  valX = (sumX >> oSampleBits); //reduce the bitdepth of the sum to OUTPUT_BITS
  valY = (sumY >> oSampleBits);
  //oversampling finished

  valX = kalmanFilterX.updateEstimate(valX); //apply Kalman filter to remove remaining gaussian noise
  valY = kalmanFilterY.updateEstimate(valY);

  /*
  //remove comment for debugging on the serial monitor/plotter
  Serial.print("X: ");
  Serial.print(valX);
  Serial.print(" Y: ");
  Serial.print(valY);
  Serial.println("");
  */

  Joystick.setXAxis(valX - CENTER_X);
  Joystick.setYAxis(-valY + CENTER_Y); //reverse pitch axis for the saitek
}

Wiring Details

2 potentiometers are wired to GND, VCC, A0 and A1

Additional context

It makes no difference what USB port is used. Tried USB 3.1 and 2.0 ports. Other Joysticks have no problem. I have tried to delay the Joystick.begin() in the setup() routine by up to 20 seconds (windows is on the desktop by then), but that didn't help.