Multi-position rotaries as analog switches as HID

I would like to integrate a multi-position rotary switch (8 steps) that has been modded with resistors, to act essentially as a potentiometer with voltage dividing network. It is built using 1k resistors wires in series from first position pin to last position pin. With this, I only need 3 wires, a ground, 5v, and signal.
This way, I save on available pins as some of my needs require 8 of the 8 steps to be used or even 12.

This is part of a cockpit I am building for DCS and Falcon BMS. In DCS, the code library, along with a piece of software, already supports inputs via its proprietary means.

I am now attempting to use a Pro Micro as an HID as an alternative means of interfacing my cockpit(or Leonardo/Uno). With this, I loose functionality of my multi-position analog switches.

My questions are:

  1. How can I code the Arduino so that the HID sketch accepts my switch as an analog input but outputs as a button press?
  2. Is it even possible to have this as a function in this form of control input?
  3. What would the alternative be for me to be able to emulate a joystick with buttons that can accept multi-position analog switch?

Thank you in advance.

Hi, @serge933

How do you loose functionality?
The ProMicro has analog input pins.

Tom.... :smiley: :+1: :coffee: :australia:

Well, I loose the ability to make say, position 4 of 8 be an actual switch press. If I connect this to my arduino and use code made for the analog potentiometers, it won't work. I need it to output a button/switch input.

Are you not using the dcs-bios library on the Micro?
How you do this depends a lot on how the code handles buttons/switches. Getting the rotary switch position is just a series of comparisons to the value from analogRead().

I was originally using DCS-Bios, but since I'm making an F-16 cockpit, I wanted it to be compatible with DCS and Falcon BMS. This means I have to set these boards up as an HID Device so I can use them on both simulators.

Could it be theoretically possible to make this happen within the joystick library?

Should be possible to treat the rotary switch as eight separate switches in the joystick library, since the library depends on your code to read the switches. A bit more complex if you want to simulate momentary buttons instead of switches.

< edit > Which joystick library are you using? I may be looking at the wrong one.

I am using the standard joystick library, just called "joystick" IIRC.

I've tried using AI to create a sketch for me to do at least one rotary switch just so I can see how it builds the code for such requirements. I know, Ai isn't the best way to learn, but this issue has had me stuck on my project for over 2 weeks now. Needed some examples.
Anyway, it bricks my pro micro every time. I have to reset it with an empty sketch.

The real loss to a HOTAS is losing an axis.

In the case of buttons and pins you can use shift registers on the SPI bus or IIRC 2 IO pins to add many-many buttons. We call shift registers "pin multipliers".

I'm not sure what you mean by losing an axis. I'm not building a HOTAS, I need replica panels to interface with the simulators so I'm not concerned about losing an axis. There aren't many axis on the panels anyway. The panels are also mostly built now, meaning if I had to rewire each individual pin, it would create so much more work for me and tear up the wiring/protoboard I already made. This would also make for an unclean design as I don't want to add more time designing a shift register board and have it made. This would be one of my last resorts. I cant seem to find any ones already made, oddly.

I will be having some 8 position rotary switches that really benefit from the ability to read the input and output via 1 wire vs 8.

I'm sure I don't understand.

If you have eight positions that can be differentiated by the one analog voltage the rotary swtich can produce…

… how do you plan to be able to "press" "button" five, as an examlke, twice in a row?

If every button press was different to the previous button press, just write logic that sees the new value form the voltage divider and turns that into a button press.

But as I wonder, how do you then get the same button pressed twice in a row?

a7

I wouldn't need to press the switch again.

This is a replica of an aircraft panel, where multi-position rotary switches are commonplace. For example, in commercial aircraft, the wiper has 4 positions. Off/park, Slow, Medium, Fast. These are hard clicking positions on the rotary switch. The idea is that the switch stays in that position for the duration and must maintain it ON. When I want it off, I simply rotate it to the Off/Park position. Just like on a car's wiper stalk.

In the F-16 I am building, there are quite a few of these examples but for different systems. No need to "press" the button twice in a row. I hope that makes more sense.

My issue is that I do not know how to code this specific need of mine. I've tried finding examples but it looks like I'll need to learn from the absolute basics. Thats why I was hoping to come on here and get some examples from those who know this better than I do. I can understand python but this is new to me, at least an example of code that works with the joystick library will get me on the right path.

You do it by binning the analog readings into N different bins, and do state-change detection on the binned variable, clearing the old "button" when you leave a state, and setting a new "button" when you enter a state.

Try:

but set the number of bins for the LHS slider to 8 with const long NumCategories = 8; as per

code
// Analog StateChange Detection for
// https://wokwi.com/projects/391124361363049473
// for https://forum.arduino.cc/t/esp8266-analog-input-triggering-issue-for-one-shot/1229757/24
// and 
// and https://forum.arduino.cc/t/using-analog-input-to-both-turn-on-and-turn-off-a-digital-output/1292092/5
// See also https://forum.arduino.cc/t/wokwi-simulations-for-arduino-built-in-examples/1304754/14?u=davex


int lastButtonState = 0; // integer to remeber a range of distinct states

const int AnalogPin = A2;
const int SensorPin = A0;

int currentButtonState = 0;
int currentSensorState;
unsigned long lastDebounceTime = 0;
unsigned long debounceDelay = 50;

void setup() {
  Serial.begin(115200);
}

void loop() {
  checkAndReportSliderA();
  checkAndReportSensor2();

}

bool checkAndReportSensor2() {
  bool changed = false;
  static int lastSensorState = -1;
  currentSensorState = analogRead(SensorPin);
  if (currentSensorState != lastSensorState) {
    changed = true;
    lastSensorState = currentSensorState;
    Serial.print(currentSensorState);
    Serial.print(" ");
  }
}

void checkAndReportSliderA() {
  // This checks an analog input,
  // bins it into categories,
  // and prints a message when the category changes
  const long NumCategories = 8;
  const int MaxADC = 1023;

  currentButtonState = analogRead(AnalogPin) * NumCategories / (MaxADC + 1); // transform into distinct ranges
  if (currentButtonState != lastButtonState && millis() - lastDebounceTime >= debounceDelay) {
    // The input has changed
    lastDebounceTime = millis(); // record the time for rate limiting
    Serial.print(num2azAZ(currentButtonState));
    lastButtonState = currentButtonState;
  }
}

char num2azAZ(int num) {
  char ch = num < 26 ?   char('a' + num) : char('A' + num - 26);
  return ch;
}
2 Likes

OK, then it is simple as I suggested. I am not familiar with the HId, but something makes a button press call to something (!), and code that used the same call could be arranged to do whenever the pot value changed enough.

Once per change.

So you need to track which position the switch is in, and react when it changes, perhaps waiting a bit to see it has finiched changing or living with a few button presses that will appear to come rapidly then stop on the now stable position.

a7

1 Like

The type of rotary switch could matter: Make-before-break/shorting would be a bit more well behaved than break-before-make/non-shorting in that the wiper wouldn't switch to open-circuit between positions.

Yeth.

I hadn't even thought of the noise, rather the need to "press" 3, 4 and 5 if 2 was the previous press and 6 the new one.

a7

1 Like

If the switch starts at the Fast position then moving it to Off/park will pass through the Medium and Slow positions. You need to take this into account

If the simulation is extremely accurate, you would probably want each intermittent switch position to show up, and to not skip any position.

1 Like

Basic internet: use a search engine like Google or Yahoo.

Please don't ask for search words before you try. It will make you look bad.

But the aircraft panel switch is not connected to a computer that checks the switch position 100,000 times per second.

Ideally, those should produce 10-bit analogRead() values like these with decision thresholds at the midpoints between the expected values:

void setup() {
  const int NPositions = 8;
  Serial.begin(115200);

  for(int pos = 0; pos < NPositions ; ++pos){
    Serial.print("Position:");
    Serial.print(pos);
    Serial.print(" val:");
    Serial.print(1023L * pos /(NPositions-1) );
    Serial.print(" upperThreshold:");
    Serial.print(1023L * pos /(NPositions-1) + 1023L/(NPositions-1)/2 );
    Serial.println();
  }
}

void loop() {
}

gives:

Position:0 val:0 upperThreshold:73
Position:1 val:146 upperThreshold:219
Position:2 val:292 upperThreshold:365
Position:3 val:438 upperThreshold:511
Position:4 val:584 upperThreshold:657
Position:5 val:730 upperThreshold:803
Position:6 val:876 upperThreshold:949
Position:7 val:1023 upperThreshold:1096

My code in #12 sets some of the thresholds lower than the midpoint of the intervals. I think the proper integer-math one-line transformation from ADC reading to bin with thresholds at the midpoints between the ideal values should be:

  // put thresholds at midpoints:
  currentButtonState = (analogRead(AnalogPin) * 2 *  (NumCategories - 1) / MaxADC + 1) / 2; // transform into distinct ranges at midpoints

The simulation is adjusted to match:

bin-at-midpoints code
// Analog StateChange Detection for
// https://wokwi.com/projects/391124361363049473
// for https://forum.arduino.cc/t/esp8266-analog-input-triggering-issue-for-one-shot/1229757/24
// and
// and https://forum.arduino.cc/t/using-analog-input-to-both-turn-on-and-turn-off-a-digital-output/1292092/5
// See also https://forum.arduino.cc/t/wokwi-simulations-for-arduino-built-in-examples/1304754/14?u=davex


int lastButtonState = 0; // integer to remember a range of distinct states

const int AnalogPin = A2;
const int SensorPin = A0;

int currentButtonState = 0;
int currentSensorState;
unsigned long lastDebounceTime = 0;
unsigned long debounceDelay = 50;

void setup() {
  Serial.begin(115200);
}

void loop() {
  checkAndReportSliderA();
  checkAndReportSensor2();

}

bool checkAndReportSensor2() {
  bool changed = false;
  static int lastSensorState = -1;
  currentSensorState = analogRead(SensorPin);
  if (currentSensorState != lastSensorState) {
    changed = true;
    lastSensorState = currentSensorState;
    Serial.print(currentSensorState);
    Serial.print(" ");
  }
}

void checkAndReportSliderA() {
  // This checks an analog input,
  // bins it into categories,
  // and prints a message when the category changes
  const long NumCategories = 52;
  const int MaxADC = 1023;

  // original thresholds just under ideal values:
  // currentButtonState = analogRead(AnalogPin) * NumCategories / (MaxADC + 1); // transform into distinct ranges

  // put thresholds at midpoints:
  currentButtonState = (analogRead(AnalogPin) * 2 *  (NumCategories - 1) / MaxADC + 1) / 2; // transform into distinct ranges at midpoints
  if (currentButtonState != lastButtonState && millis() - lastDebounceTime >= debounceDelay) {
    // The input has changed
    lastDebounceTime = millis(); // record the time for rate limiting
    Serial.print(num2azAZ(currentButtonState));
    lastButtonState = currentButtonState;
  }
}

char num2azAZ(int num) {
  char ch = num < 26 ?   char('a' + num) : char('A' + num - 26);
  return ch;
}
1 Like