Jump to content

Recommended Posts

Posted (edited)

I've made a Vertical Speed Indicator for my F-18 cockpit and the issue I'm having is that the dial is non-linear, where the DCS BIOS values of 0 to 65535 aren't just 1-to-1 across the dial (smaller steps in the -1000 to 1000 ft/min range). Does anyone know how the mapping should be done to account for this? Below is code that should work if the tick-marks on the indicator were a constant distance (excuse my long comments in it as its my first mechanical dial I'm making).

void onVsiChange(unsigned int newValue) {
  /*
    Try and use the map function to translate the game values (0-65535) to the stepper
    position (0-4096). This approach requires the zero of the dial to match what it is
    in the game, so 0 -> -6000 ft/min on the dial. The maximum isn't a full circle, 
    instead it is 340 degrees. That means the maximum position for the stepper is 
    227.556 or 228 steps less at 3868.
  */
  unsigned int adjValue = 0;  // this will hold the translated value for the stepper
  /*
    The map function in arduino takes one range of numbers and maps it to a new range. This
    allows us to translate the game values to stepper values. This mapping is linear.
  */
  adjValue = map(change, 0, 65535, 0, 3868);
  verticalSpeedStepper.moveTo(adjValue);  // send the stepper to the new location
  verticalSpeedStepper.setSpeed(MAX_SPEED);  // move uses acceleration, to be constant and max speed we force it here    
}
DcsBios::IntegerBuffer vsiBuffer(FA_18C_hornet_VSI, onVsiChange);

 

Edited by AlphaDecay
Posted (edited)

@AlphaDecay

If you are familiar with Arduino coding, you could use Rob Tillaart's MultiMap library for non linear mapping problems.

You just need two arrays of data (input and output). They have to have the same size to get mapped.

I suggest to multimap the VSI integer to the scale value of the gauge (motor steps).

 

Regards, Vinc

Edited by Vinc_Vega
Link edited
  • Like 1

Regards, Vinc

real life: Royal Bavarian Airforce

online: VJS-GermanKnights.de

[sIGPIC][/sIGPIC]

Posted

That was the approach @Vinc_Vega!! I've included my code below for anyone else trying to figure it out.

#define DCSBIOS_IRQ_SERIAL
#include "DcsBios.h"
/*
  Need to map game values (0-65535) to stepper motor positions (0-4096), so map()
  for arduino doesn't work as some dials have non-linear regions like the F-18
  vertical speed indicator and airspeed indicator. MultiMap tries to solve this 
  problem with piece-wise linear interpolation provided a series of data points.
*/
#include "MultiMap.h"
/*
  Need to create equally sized arrays representing the reference points. For this
  the datatype needs to be the same (integer in this case).

  inputArray[]
    *must have increasing values, this comes from the game 0-65535
    *does not have to be equal distance between values
  outputArray[]
    *multiMap will constrain to the first/last values of this array which is good
     as that acts as physical stops for the stepper and won't try and send it past
     the ends of the physical dial (which would have physical limit stops).
    *The output array requires that you go into the game and place the plane into
     those specific values (e.g. 1500 ft/min climb) and then look at DCS Bort to see
     what value it is providing in the range it gives.
    *As the collected values will/should correspond to physical locations on the
     instrument, now you get the angle position for each of those collected values
     as measured on the Fusion design for the instrument and then convert each of
     those positions to a number of steps for your stepper motor. These steps are what
     go into the corresponding locations for MultiMap. E.g. in game at a -3500 ft/min
     descent, DCS Bort says 7063, in Fusion that is 57.5 degrees from the -6000 mark,
     and my stepper needs 654 steps to go that angle.
  
  Default multiMap() does an in-order search from beginning to end and then interpolates
  between values. So that means larger input values take longer to "find" for a long list.
  At about 10 entries it is parity to do a binary search, so that is what I use here since
  an instrument like the Vertical Speed Indicator has 41 entries. The search appears to work
  better with floats, will round to an int before use.
*/
float vsiInputArray[41] = {
  0,1172,2535,4016,5460,7063,8712,10774,12846,16277,19707,20640,22036,23322,24114,25547,27025,28455,29720,31381,32767,
  34153,35814,37079,38509,39987,41420,42212,43498,44894,45827,49257,52688,54760,56822,58471,60074,61518,62999,64362,65535
};
float vsiOutputArray[41] = {
  0,85,228,370,512,654,796,939,1081,1223,1365,1422,1479,1536,1593,1650,1707,1764,1820,1877,1934,
  1991,2048,2105,2162,2219,2276,2332,2389,2446,2503,2645,2788,2930,3072,3214,3356,3499,3641,3783,3868
};
float vsiNewPosition = 0;  // this will hold the translated value for the stepper, it is zeroed with stepper to 0

/*
 Normally would include Stepper.h but the default stepper approach
 only does one stepper at a time, doesn't support acceleration, and when 
 stepping a motor it blocks other things from happening. So we will use
 a better library.
 */
#include <AccelStepper.h>
/*
  Running in constant speed mode, so we set the max and current speed to the
  same value. These values were experimentally determined. Any faster and it 
  seems to jitter. We have a 4 wire motor, so the interface type is 4. We can 
  run in half speed by setting it to 8 apparently.
*/
#define MAX_SPEED 850
#define MAX_ACCEL 850
#define MotorInterfaceType 8
/*
  Initialize the pins, just change the values to how it's connected
  myStepper(MotorInterfaceType, Blue, Yellow, Pink, Orange);
  Blue is IN1
  Pink is IN2
  Yellow is IN3
  Orange is IN4
*/
AccelStepper verticalSpeedStepper(MotorInterfaceType, 45, 49, 47, 51);

// setup the photo-interrupter to determine where zero will be on the dial
const int verticalPhotoInterrupter = 53;  // pin number

/*
  Next we setup some variables and constants for doing a zero and for moving
  the stepper to various locations.
*/
#define VERTICAL_ZERO_OFFSET 0  // this allows fine-tuning to get zero non-mechanically
bool isVerticalZeroed = 0;  // have to give stepper time to zero and do process only once

void onVsiChange(unsigned int newValue) {
  // Use multiMapBS to convert the game value to a stepper position
  vsiNewPosition = multiMapBS<float>(newValue, vsiInputArray, vsiOutputArray, 41);
  int vsiMovePosition = round(vsiNewPosition); // multiMap was done as a float round it to int
  int vsiCurrPosition = verticalSpeedStepper.currentPosition();  // get where the stepper is now
  int relativeMove = vsiMovePosition - vsiCurrPosition;  // how far to move, final - initial as CW is positive CCW is negative
  verticalSpeedStepper.move(relativeMove);  // send the stepper to the new location via a relative move
  verticalSpeedStepper.setSpeed(MAX_SPEED);  // move uses acceleration, to be constant and max speed we force it here    
}
DcsBios::IntegerBuffer vsiBuffer(FA_18C_hornet_VSI, onVsiChange);

void setup() {  
  // Get DCS BIOS Setup
  DcsBios::setup();
  // set the speed at which the stepper is to run
  verticalSpeedStepper.setMaxSpeed(MAX_SPEED);
  verticalSpeedStepper.setSpeed(MAX_SPEED);
  verticalSpeedStepper.setAcceleration(MAX_ACCEL);
  // check if in the photointerrupter, then move to the right edge
  while(!isVerticalZeroed) {
    // currently stopped in the photo-interrupter module, let's move out to get a good zero
    if (digitalRead(verticalPhotoInterrupter) == HIGH && verticalSpeedStepper.distanceToGo() == 0) {
      verticalSpeedStepper.move(80);
      verticalSpeedStepper.setSpeed(MAX_SPEED);
      verticalSpeedStepper.runSpeedToPosition();
    }else {
      verticalSpeedStepper.runSpeedToPosition();
    }
    // currently stopped but not in the module
    if (digitalRead(verticalPhotoInterrupter) == LOW && verticalSpeedStepper.distanceToGo() == 0) {
      while(digitalRead(verticalPhotoInterrupter) == LOW) {
        verticalSpeedStepper.setSpeed(MAX_SPEED);
        verticalSpeedStepper.runSpeed();
      }
      verticalSpeedStepper.setCurrentPosition(0);
      isVerticalZeroed = 1;
    }
  }
  //give the steppers time to get to their zero position
  delay(5000);
}

void loop() {
  // Run the DCS BIOS Loop
  DcsBios::loop();
  verticalSpeedStepper.runSpeedToPosition();
}

 

Posted (edited)

@AlphaDecay 

Congratulations, well done! 👍

Nevertheless, I want to give some hints to consider for better performance of stepper motor gauges.

As the stepper has physical end stops, why the need for a photoresistor to find the zero position? It is used once after power up and than never again. It's not an Altimeter with endless moving needles.

A simple way is to run the stepper against the lower or the high end stop. Tell the stepper its position and than run to your physical gauge zero marking. You have to find the right step amount by experimenting. Tell the stepper the new zero position or use  offsets. Ensure that the total way of the needle than never reaches behind the end stops. That's also the manufacturer's recommendation for calibration. For that procedure you may use a lower speed as in the main sketch. So after calibration a new max speed may be set.

You don't need to set the speed everytime the poisition changes in the "void onVsiChange(...)" part, it already should be defined in the Setup section and than never changes.

To effectively reduce calculation efforts and maybe thereby introduced jitter, I recommend a lower resolution of the mapping. Don't use more than 10 to 15 values per array. That will speed up your calculations.

All values (newValue and steps to go) are not higher than 65535, so integer data types would reduce memory and calculation efforts. Especially the arrays reserve much more memory when using floats. Anyway, you don't need floats to produce the stepper counts. Mapping even should produce rounded values (integers).

I hope that further reduces the mentioned jitters.

 

Regards, Vinc

Edited by Vinc_Vega
error corrections

Regards, Vinc

real life: Royal Bavarian Airforce

online: VJS-GermanKnights.de

[sIGPIC][/sIGPIC]

Posted

Thanks again for the input. The steppers I have do full rotations - I teach aerospace engineering and had some laying around from some robotics projects. The downside is needing the photogate to zero it. So far in testing it seems to just work and doesn't take much time really to accomplish the task. On the max speed thing - the documentation on AccelStepper says to call a setSpeed after calling moveTo for constant speed motion or it will use acceleration. While I had concerns about abruptly changing direction (basically running it at max speed all of the time) - it seems to work so far in limited testing. I haven't done any longer play with it though so I don't know if there are errors that will accumulate. I'll also play with the number of elements in the array if it seems to get laggy as I add devices - it just seemed to make sense at the time to put an element for each known marked position on the dial to improve accuracy at least for those landmarks. That arduino should only be running 3 steppers (each with own power) and some LEDs. I'm doing all of the buttons and switches with a Bodnar card so I can maximize cross-game use of those.

Posted (edited)

Ah, okay. We usually use these cheap VID type automotive instrument steppers, including end stops, for direct connection to Arduinos. They don't pull much power and are good parts for Home Cockpit building 😉

Be careful if connecting other type of steppers without the use of additional driver boards. Arduino pins output 5V and are limited to max 50mA. But I think you already have that in mind for your 3 steppers project.

If you want to display accurat values, than 41 element arrays make sense. But it will cost a lot of calculation power compared to only 10 or 15 elements. I don't believe that you can see a remarkable difference if you'd reduce the mapping resolution.

If you want to drive more than one stepper from a single Arduino at the same time, there is a specific way to do so with the AccelStepper library.

If you like, I'll than could post some code snippets here. I did that for two steppers at the same time. I think that driving 3 steppers may be possible.

 

Regards, Vinc

Edited by Vinc_Vega

Regards, Vinc

real life: Royal Bavarian Airforce

online: VJS-GermanKnights.de

[sIGPIC][/sIGPIC]

Posted

Attached a video of it working. If anything, the stepper is on the slow side and while it lags under large deflections (end of video), even after hitting the limit stops and stuff it doesn't seem to lose accuracy. When flying that crazy one isn't looking at their VSI anyway.

44 minutes ago, Vinc_Vega said:

Ah, okay. We usually use these cheap VID type automotive instrument steppers, including end stops, for direct connection to Arduinos. They don't pull much power and are good parts for Home Cockpit building 😉

Be careful if connecting other type of steppers without the use of additional driver boards. Arduino pins output 5V and are limited to max 50mA. But I think you already have that in mind for your 3 steppers project.

If you want to display accurat values, than 41 element arrays make sense. But it will cost a lot of calculation power compared to only 10 or 15 elements. I don't believe that you can see a remarkable difference if you'd reduce the mapping resolution.

If you want to drive more than one stepper from a single Arduino at the same time, there is a specific way to do so with the AccelStepper library.

If you like, I'll than could post some code snippets here. I did that for two steppers at the same time. I think that driving 3 steppers may be possible.

 

Regards, Vinc

 

Yup for sure.. Here's a picture of the assembly with the driver board (green) and the supplementary power at the back. Always happy to see code as it exposes new ideas to me as I'm not formally trained or anything.

image.png?ex=66789291&is=66774111&hm=506

  • Like 1
Posted

You might find this of use.
 

-- Vertical Speed Indicator AVU-53/A
Variometer					= CreateGauge()
Variometer.arg_number		= 225
Variometer.input			= {-6000.0, -4000.0, -3000.0, -2000.0, -1000.0, -500.0, 0.0, 500.0, 1000.0, 2000.0, 3000.0, 4000.0, 6000.0}
Variometer.output			= {   -1.0,   -0.83,   -0.73,  -0.605,   -0.40,  -0.22, 0.0,  0.22,   0.40,  0.605,   0.73,   0.83,    1.0}
Variometer.controller		= controllers.Variometer

This is how the game maps the VSI values to the cockpit model needle positions.  Output -1 is needle fully anticlockwise, +1 is fully clockwise.  Just 13 mapping points.

This information, along with other non-linear gauge mapping info, comes from the "mainpanel_init.lua" file in the module's "cockpit/scripts" folder.

Posted (edited)
19 hours ago, No1sonuk said:

You might find this of use.
 

-- Vertical Speed Indicator AVU-53/A
Variometer					= CreateGauge()
Variometer.arg_number		= 225
Variometer.input			= {-6000.0, -4000.0, -3000.0, -2000.0, -1000.0, -500.0, 0.0, 500.0, 1000.0, 2000.0, 3000.0, 4000.0, 6000.0}
Variometer.output			= {   -1.0,   -0.83,   -0.73,  -0.605,   -0.40,  -0.22, 0.0,  0.22,   0.40,  0.605,   0.73,   0.83,    1.0}
Variometer.controller		= controllers.Variometer

This is how the game maps the VSI values to the cockpit model needle positions.  Output -1 is needle fully anticlockwise, +1 is fully clockwise.  Just 13 mapping points.

This information, along with other non-linear gauge mapping info, comes from the "mainpanel_init.lua" file in the module's "cockpit/scripts" folder.

I'll give that a try, thanks for the info. Better than trying to reverse engineer the values. 

EDIT: It's the next day - worked great on the airspeed indicator. I'll update my vertical speed once I can get around to it. I need to put some thought into the issue that DCS BIOS seems to output 0 to 65535, while the game uses the indicator as zero going up and down from there. Currently I just have the needle match DCS BIOS and start at -6000 ft/min and work from there as the 0.

Edited by AlphaDecay
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...