Jump to content

Honey, I developed FFB joystick (DIY)


Recommended Posts

On 10/10/2021 at 10:15 AM, propeler said:

It is finished, it is working.

 

No they don't. All components are conected.

 

 

Hi,

Can somebody explain me the process to order the @propeler / olukelo premount PCB for this FFB joystick? I tried via EasyEDA for JLPCB but the bridge between the two websites seems to be broken.

After that which BOM should I buy (for composant not mounted by EasyEDA)  ?

Thanks

 


Edited by flyingtux
Link to comment
Share on other sites

Between adding always-active damping and dropping odrv0.axis0.motor.config.current_control_bandwidth from the default of 2000 down to 100 it seems like I solved my out of control oscillation problems at higher torques. Motors are nice and quiet too.

Now the limiting factor really is the strength of the printed gimbal joint, and all that vibration has put that "clunk" back in to the roll axis, even with re-tightening everything down 😑

 

 

  • Like 1
Link to comment
Share on other sites

  • 1 month later...
On 2/7/2022 at 10:02 AM, TomVR said:

Any ideas how to reduce the slop in it?

In precision machining (in metal), screws are avoided for positioning. Instead you'll usually find pins indexing the position of components (in reamed holes, so the diameters have specific fit tolerances), and screws holding the parts together.  Given that we're 3d printing everything, I'm not sure we can get enough precision for pins to be relevant, but if we could, that's where I'd start. In 3d printing I tend to just torque things down but you can only go so far before you start to crack things. 😞

Link to comment
Share on other sites

3 hours ago, Alterscape said:

In precision machining (in metal), screws are avoided for positioning. Instead you'll usually find pins indexing the position of components (in reamed holes, so the diameters have specific fit tolerances), and screws holding the parts together.  Given that we're 3d printing everything, I'm not sure we can get enough precision for pins to be relevant, but if we could, that's where I'd start. In 3d printing I tend to just torque things down but you can only go so far before you start to crack things. 😞

I wonder if then instead running a shoulder bolt from the top and putting a nut on the bottom would work better

https://www.amazon.ca/uxcell5pcs-Stainless-Socket-Shoulder-Length/dp/B077GWSV23/ref=sr_1_47?crid=1ZYZRE2FM43G1&keywords=5mm+shoulder+bolt&qid=1644431485&sprefix=5mm+shoulder+bolt%2Caps%2C60&sr=8-47


Edited by TomVR
Link to comment
Share on other sites

12 hours ago, TomVR said:

I cut the head off a m5x50 shoulder bolt, put it in the existing hole with a flange nut on the bottom and a shaft collar at the top.

Link to comment
Share on other sites

  • 2 weeks later...

I just ordered the bolts that @TomVR suggested. I haven't started any final assembly, so I'll post how it goes. 

I printed almost everything in PETG witih 100% infill. So I think it'll be pretty strong.

My intention is to take this beyond just FFB. I'd like to be able to program a helicopter autopilot that can be fed with DCS, X-Plane, or maybe even MSFS.

Link to comment
Share on other sites

I was having issues with stability where the joystick would randomly stop working and need to be reset, once every couple of days/sessions, so I did a couple of things that seem to have cured it.

Firstly, I replaced the chinese pro micro clone with a genuine sparkfun one, and installed their drivers for it.

Second, I changed my code to use my own odrive->arduino comms instead of using the ODriveArduino library. This avoids the use of arduino Strings to try and eliminate possible memory issues, clears the serial buffer before reading/writing on it, and overall reduces the number of characters being written on the serial port which makes things faster. I also made sure to give the arduino some "idle" time instead of trying to get as many position updates as possible.

I also realized that my settings for the motors and torque commands meant I was saturating my set current limit and was clipping the feedback. You should set the odrive's motor torque constant parameter correctly, and send the commands in units of torque instead of units of current.

 

Here's the updated code. Be warned that it's still pretty confusing and mostly hacked together bull<profanity>. If you've got a question I'd be happy to try and explain.

Replace the files in the Fino project with the contents here:

Fino.ino:

#include "src/Joystick.h"
#include "config.h"
#include <HardwareSerial.h>

// Printing with stream operator helper functions
template<class T> inline Print& operator <<(Print &obj,     T arg) {
  obj.print(arg);
  return obj;
}
template<>        inline Print& operator <<(Print &obj, float arg) {
  obj.print(arg, 4);
  return obj;
}
// -------------------------
// Various global variables
// -------------------------
unsigned long lastEffectsUpdate;
unsigned long nextJoystickMillis;
unsigned long nextEffectsMillis;

// --------------------------
// Joystick related variables
// --------------------------
#define minX -10000
#define maxX 10000
#define minY -10000
#define maxY 10000


//periodicGain sets the gains for everything that isnt a spring, friction, or damping force
const float periodicGain = 0.5;
const float extraDampingGain = 0.01;
int dampingEnabled = 0;

bool is_connected = false;
bool forces_requested = false;
bool pos_updated = false;
bool newData = false;

const byte numChars = 32;
char receivedChars[numChars];
char tempChars[numChars];        // temporary array for use when parsing

float rcvdPos[2];   //uses odrive notation for motors
float rcvdVel[2];   //uses odrive notation for motors

int16_t pos[2] = {0, 0};
int lastX;
int lastY;
int lastVelX;
int lastVelY;
int lastAccelX;
int lastAccelY;

float scaleFactorX;
float scaleFactorY;

//because odrive reports in motor rotations we have to do some converting
//these are the limits and center for my joystick in odrive-reported-rotations
const float maxXRot = 0.13;
const float minXRot = -0.11;
const float maxYRot = -0.23;
const float minYRot = 0.17;
const float centerXRot = 0.02;
const float centerYRot = -0.03;


EffectParams effects[2];
int32_t forces[2] = {0, 0};

Joystick_ Joystick(
    JOYSTICK_DEFAULT_REPORT_ID, JOYSTICK_TYPE_JOYSTICK,
    0, 0, // Button Count, Hat Switch Count
    true, true, false, // X, Y, Z
    false, false, false, // Rx, Ry, Rz
    false, false); // rudder, throttle

void setup() {
    //Makes uploading the new sketch easier, because serial may get flooded
    //hit the reset pin as it finishes compiling
    delay(1000);
    setupJoystick();

    // setup timing and run them as soon as possible
    lastEffectsUpdate = 0;
    nextJoystickMillis = 0;
    nextEffectsMillis = 0;
    dampingEnabled = 0;
}

void loop(){
    //i setup my odrive as motor 1 is x, 0 is y :bigbrain:
    unsigned long currentMillis;
    currentMillis = millis();

    //wait 2 seconds for encoder signal to calm down and make sure theres no velX funny business
    if(currentMillis > 2000){
      dampingEnabled = 1;
    }
    
    // do not run more frequently than these many milliseconds
    if (currentMillis >= nextJoystickMillis) {
      
        getFeedback(1);

        getFeedback(0);

        pos[0] = (int)((rcvdPos[1]-centerXRot)*scaleFactorX);
        pos[1] = (int)((rcvdPos[0]-centerYRot)*scaleFactorY);
        
        updateJoystickPos();
        nextJoystickMillis = currentMillis + 8;
        

        // we calculate condition forces every 50ms or more frequently if we get position updates
        if (currentMillis >= nextEffectsMillis || pos_updated) {
            updateEffects(true);
            nextEffectsMillis = currentMillis + 50;
            pos_updated = false;
        } else {
            // calculate forces without recalculating condition forces
            // this helps having smoother spring/damper/friction
            // if our update rate matches our input device
            updateEffects(false);
        }
        
        setTorque(1,(forces[0]*(0.1378*7.5/10000.0))+(effects[0].damperVelocity*-extraDampingGain*dampingEnabled));
        setTorque(0,(-forces[1]*(0.1378*10/10000.0))+(effects[1].damperVelocity*extraDampingGain*dampingEnabled*2));
    }
}

 

com.ino:

void setOdriveState(int axis, int requested_state) {
  Serial1 << "w axis" << axis << ".requested_state " << requested_state << '\n';
}

void setTorque(int axis, float torque) {
  Serial1 << "c " << axis << " " << torque << "\n";
}


void getFeedback(int motor_number){   //use odrive numbers for motors
  //clear the input buffer
  while(Serial1.available()>0){
    Serial1.read();
    }
  Serial1 << "f " << motor_number << "\n";
  recvFeedback(motor_number);
}

void recvFeedback(int motor_number) {
  static byte ndx = 0;
  char endMarker = '\n';
  char rc;
  static const unsigned long timeout = 10;
  unsigned long timeout_start = millis();
  bool not_timed_out = true;
  while (newData == false && not_timed_out) {
    if (Serial1.available() > 0) {
      rc = Serial1.read();

      if (rc != endMarker) {
        receivedChars[ndx] = rc;
        ndx++;
        if (ndx >= numChars) {
          ndx = numChars - 1;
        }
      }
      else {
        receivedChars[ndx] = '\0'; // terminate the string
        ndx = 0;
        newData = true;
      }
    }
    else if (millis() - timeout_start >= timeout) {
        not_timed_out = false; //taking too long, abort
      }
    }

    if(newData){    //we must have timed out, so don't copy in new data
      strcpy(tempChars, receivedChars);
      char * strtokIndx; // this is used by strtok() as an index
      strtokIndx = strtok(tempChars, " ");     // get the first part - the string
      rcvdPos[motor_number] = atof(strtokIndx);      
      strtokIndx = strtok(NULL, ",");     // this continues where the previous call left off
      rcvdVel[motor_number] = atof(strtokIndx);     // convert this part to a float
      newData = false;    //reset for next read      
      pos_updated = true;
  }
}

 

joystick.ino:

void setupJoystick() {
    scaleFactorX = (2*maxX) / (maxXRot - minXRot);
    scaleFactorY = (2*maxY) / (maxYRot - minYRot);
    Joystick.setXAxisRange(minX, maxX);
    Joystick.setYAxisRange(minY, maxY);
    Joystick.begin();
    Gains gains[FFB_AXIS_COUNT];
    gains[0].springGain = 1.0;
    gains[1].springGain = 1.0;
    gains[0].frictionGain = 0.25;
    gains[1].frictionGain = 0.25;
    gains[0].squareGain = periodicGain;
    gains[1].squareGain = periodicGain;
    gains[0].sineGain = periodicGain;
    gains[1].sineGain = periodicGain;
    gains[0].triangleGain = periodicGain;
    gains[1].triangleGain = periodicGain;
    gains[0].sawtoothupGain = periodicGain;
    gains[1].sawtoothupGain = periodicGain;
    gains[0].sawtoothdownGain = periodicGain;
    gains[1].sawtoothdownGain = periodicGain;
    gains[0].damperGain = 0; //added always-active damping elsewhere
    gains[1].damperGain = 0;
    
    Joystick.setGains(gains);
    Serial1.begin(500000);

    delay(100);
    //clear the input buffer
    while(Serial1.available()>0){
      Serial1.read();
    }
    setOdriveState(0, 8); //AXIS_STATE_CLOSED_LOOP_CONTROL = 8
    delay(100);    
    setOdriveState(1, 8); 
    delay(100);    
}

void updateJoystickPos() {
    Joystick.setXAxis(pos[0]);
    Joystick.setYAxis(pos[1]);
}

void updateEffects(bool recalculate){
    for (int i =0; i < 2; i++) {
        effects[i].frictionMaxPositionChange = 15; // TODO: find proper values for these automatically
        effects[i].inertiaMaxAcceleration = 5;
        effects[i].damperMaxVelocity = 50;
    }

    effects[0].springMaxPosition = maxX;
    effects[1].springMaxPosition = maxY;
    effects[0].springPosition = pos[0];
    effects[1].springPosition = pos[1];

    unsigned long currentMillis;
    currentMillis = millis();
    int16_t diffTime = currentMillis - lastEffectsUpdate;

    if (diffTime > 0 && recalculate) {
        lastEffectsUpdate = currentMillis;
        int16_t positionChangeX = pos[0] - lastX;
        int16_t positionChangeY = pos[1] - lastY;
        int16_t velX = positionChangeX / diffTime;
        int16_t velY = positionChangeY / diffTime;
        int16_t accelX = ((velX - lastVelX) * 10) / diffTime;
        int16_t accelY = ((velY - lastVelY) * 10) / diffTime;
    
        effects[0].frictionPositionChange = velX;
        effects[1].frictionPositionChange = velY;
        effects[0].inertiaAcceleration = accelX;
        effects[1].inertiaAcceleration = accelY;
        effects[0].damperVelocity = velX;
        effects[1].damperVelocity = velY;

        lastX = pos[0];
        lastY = pos[1];
        lastVelX = velX;
        lastVelY = velY;
        lastAccelX = accelX;
        lastAccelY = accelY;
    } else {
        effects[0].frictionPositionChange = lastVelX;
        effects[1].frictionPositionChange = lastVelY;
        effects[0].inertiaAcceleration = lastAccelX;
        effects[1].inertiaAcceleration = lastAccelY;
        effects[0].damperVelocity = lastVelX;
        effects[1].damperVelocity = lastVelY;
    }

    Joystick.setEffectParams(effects);
    Joystick.getForce(forces);
}

 

Eventually I'd like to speed things up even further by just reading the position in as an int and avoid all the float math.


Edited by theperson
  • Like 1
  • Thanks 1
Link to comment
Share on other sites

  • 3 weeks later...
On 10/5/2021 at 9:33 PM, TomVR said:

Travelling again for work so don't have a chance to actually open up my MSFFB and see what the as designed gear reduction is, reading conflicting posts that its either 25:1 or 16:1

I've been going back through the thread, planning things when I noticed this comment. In the past I've been looking for this ratio as well but could never find a decent answer.

For the record, the reduction ratio of the MSFFB is 19.05:1 (basically 19:1).

I was looking at my MSFFB trying to figure out if there was anything I could do to it to swap the gears for belts and make it more compact and mountable to a VPC desk mount. Part of this was taking the gear train apart and actually counting the teeth. Gear 1 is 30 teeth, gear 2 is 100 teeth on the large gear and 21 teeth on the small gear. Gear 3 (the partial gear) is 20 teeth in a 60 degree arc (120 teeth for a full 70mm gear).


Edited by 98abaile
Link to comment
Share on other sites

  • 2 weeks later...
On 9/30/2021 at 7:15 AM, walmis said:

Hey guys, an update on my FFB development. 

Ordered FFB controller boards and boards for the motor control, redesigned the gimbal from scratch, so not to be bound by copyrights. Also spent some time on optimizing the design, so it could be built rather quickly. Finding sources of parts is also a challenge during these times of various shortages.

I'm hoping I will be able to start selling these electronics kits for anyone that wants to build own FFB joysticks. And later some finished builds, similarily how Prusa started with 3D printers 🙂

Added some prototype photos

Screenshot from 2021-09-30 14-20-48.png

 

 

@walmis I'm sorry if I missed it, but can you share more info about this GUI for adjusting the forces?  I have a Odrive, some motors, encoders and power supply.  I want to use them to make a cyclic for helicopter sim.  Possibly add FFB in the future, but I'm more interested in  friction, damping and trim adjustment for the cyclic than full FFB.  I have been through the mechanical dampers, springs, etc in cyclic design and never been happy with the results (homebuilt and commercial options on the market).  The mechanical aspect of building it is not an issue for me.  I have a VMC to make parts, but I need programming help. 

Link to comment
Share on other sites

Been super busy last few months developing hardware/firmware/software for my FFB system. Pleased to say it's nearing release. I'm also building a small batch of fully plug and play FFB units and drive kits. Gonna do a photo shoot this week so will have more media to share 🙂

If anyone's interested in buying a DYI motor/controller kit contact me via PM, I'll need to gauge how many components I'll need to preorder in advance.

 

Here's a sneak peek of the software side state:

Screenshot from 2022-03-27 10-57-14.png

Screenshot from 2022-03-26 12-45-34.png

This is the system diagram of connections for the DYI kit. 

ffb-diagram.png

And the assembled motor unit itself:

IMG_20220327_110034.jpg

  • Like 5
Link to comment
Share on other sites

Hello, sorry if i have missed it in the thread,

but has anyone yet compared it side to side to an unmodified Force Feedback 2 in the terms of latency, variety of forces etc? 

I was modifying my FF2 with custom grip (3d printed) while keeping the twist handle and hand hold sensor, unfortunately mbo died before project was finished. It used an arduino leonardo for custom components.

276172910-341960037898776-42877248676037Capture.jpg

 

Perhaps this project would be a good starting project to revive my FF,

has anyone investigated maybe what could be reused from stock FFB?

Edit: I was able to repair the FF2 MBO, however once winwing grip arrive will give it a shot to make a FF base. 


Edited by bpremus
  • Like 2

AMD FX 8350 4.0Ghz, 16gb DDR3, MSI R9 390, MS FF2 & CH Throttle PRO, track IR 4 & Lenovo Explorer VMR

Link to comment
Share on other sites

On 3/30/2022 at 5:40 PM, slimheli said:

I've been busy! Need to order some pulleys and belts. Looking forward to actually firing this thing up.

IMG_2208.JPG

 

Tell us about your print settings. I just started printing all the needed parts but didn't know how strong to make it. Currently I'm using PLA+ (stiffer than PETG) with 10 perimeters, 10 top and bottom layers and 10% gyroid infill.

Link to comment
Share on other sites

On 3/28/2022 at 7:42 AM, walmis said:

Been super busy last few months developing hardware/firmware/software for my FFB system. Pleased to say it's nearing release. I'm also building a small batch of fully plug and play FFB units and drive kits. Gonna do a photo shoot this week so will have more media to share 🙂

If anyone's interested in buying a DYI motor/controller kit contact me via PM, I'll need to gauge how many components I'll need to preorder in advance.

 

Here's a sneak peek of the software side state:

Screenshot from 2022-03-27 10-57-14.png

Screenshot from 2022-03-26 12-45-34.png

This is the system diagram of connections for the DYI kit. 

ffb-diagram.png

And the assembled motor unit itself:

IMG_20220327_110034.jpg

I need this soooo badly 😄

I know you're trying to make the kits as flexible as possible, but any chance of creating an accompanying design that creates a completed kit that we can cut/print ourselves?

  • Like 1
Link to comment
Share on other sites

4 hours ago, RoyMi6 said:

I need this soooo badly 😄

I know you're trying to make the kits as flexible as possible, but any chance of creating an accompanying design that creates a completed kit that we can cut/print ourselves?

Read through the thread. TomVR posted his github with a 3D printable version of the base.

  • Like 2
Link to comment
Share on other sites

On 3/28/2022 at 4:18 AM, bpremus said:

has anyone investigated maybe what could be reused from stock FFB?

Most of the parts are useless if you're going for anything with much torque.  There's all kinds of builds out there that re-use the FFB2 gimbal (often combining 2 into one unit), but IMHO none of those are adequate for a real, quality stick with good force.  However, the board itself is another story.  If you're like me and you're handier with a soldering iron than you are with a code editor, then the board itself is a fantastic resource for building your own stick.   

If you look farther up this thread you'll see some posts of mine regarding my FFB2 build.  I think I've pushed the FFB2 board probably farther than anyone else has in terms of current, at least farther than anyone I'm aware of.  And you could push it even farther with a breakout board and discrete P and N mosfets instead of the little P/N mosfet pairs it uses.  I do know that there is one other guy who has actually done a breakout board like that, although I think my build pushes more current than his.  Anyway, the FFB2 board is a very good piece of hardware that can withstand some severe modifications.

Here's a link to a Reddit post I made with some photos and description of the build.

https://www.reddit.com/r/hoggit/comments/py7525/ive_seen_some_love_for_the_microsoft_sidewinder/

 And I've got various posts up above about it.  

If you are interested in doing an FFB2 build I would be happy to get you a parts list and help with any questions you might have.  I have spent many many hours learning about modifications to the FFB2.  I keep meaning to make a dedicated thread to function as an easy "FFB2 Reference Guide," with the details of my own build and with links to many other good builds I have seen.  But have not got around to it yet.  But I'm happy to answer questions.  


Edited by Bard_the_Bowman
  • Like 1
Link to comment
Share on other sites

vor 5 Stunden schrieb Bard_the_Bowman:

Most of the parts are useless if you're going for anything with much torque.  There's all kinds of builds out there that re-use the FFB2 gimbal (often combining 2 into one unit), but IMHO none of those are adequate for a real, quality stick with good force.  However, the board itself is another story.  If you're like me and you're handier with a soldering iron than you are with a code editor, then the board itself is a fantastic resource for building your own stick.   

If you look farther up this thread you'll see some posts of mine regarding my FFB2 build.  I think I've pushed the FFB2 board probably farther than anyone else has in terms of current, at least farther than anyone I'm aware of.  And you could push it even farther with a breakout board and discrete P and N mosfets instead of the little P/N mosfet pairs it uses.  I do know that there is one other guy who has actually done a breakout board like that, although I think my build pushes more current than his.  Anyway, the FFB2 board is a very good piece of hardware that can withstand some severe modifications.

Here's a link to a Reddit post I made with some photos and description of the build.

https://www.reddit.com/r/hoggit/comments/py7525/ive_seen_some_love_for_the_microsoft_sidewinder/

 And I've got various posts up above about it.  

If you are interested in doing an FFB2 build I would be happy to get you a parts list and help with any questions you might have.  I have spent many many hours learning about modifications to the FFB2.  I keep meaning to make a dedicated thread to function as an easy "FFB2 Reference Guide," with the details of my own build and with links to many other good builds I have seen.  But have not got around to it yet.  But I'm happy to answer questions.  

 

Do you think that there is a way to control an odrive from a FFB2 board? Maybe converting the signals that steer the mosfets to something an odrive can understand (RC PWM or step/dir)? Its so hard to find inexpensive DC motors with a skewed rotor to improve the torque smoothness.
A "FFB2 Reference Guide" is highly welcome. It would be very nice to have all the information in one place and not scatterd all over the internet.

  • Like 1
Link to comment
Share on other sites

First release of device maintenance page for my FFB controller is ready! 
The controllers firmware will be upgrade'able over WebUSB. 🙂 I'll probably move away from desktop app in the future to full configuration over the web.

image.png
You can find it here:

https://vpforcecontrols.com/usb/rhino/

Supporting stuff is now done, now I finally can start prepping the DYI kits for shipping 🙂

  • Like 6
  • Thanks 1
Link to comment
Share on other sites

  • Recently Browsing   0 members

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