2DOF harness tensionner with Fly PTmover

Hi!

My cockpit is static, I've build a pressure gSeat with bladders and RC servo.
I wanted to increase immersion by a harness tensionner.

I'd already tested a static harness tensionner on a 2 DOF plateform, and the feeling was great.
As my rig is static, I'll have to actuate the harness but the advantage is that I can make a 2 DOF tensionner.

principle:
2 DOF harness -> the strength on the right shoulder and left shoulder will be different

both will react along Surge (longitudinal acceleration)
and Sway (lateral acceleration) will tight one shoulder or the other (depending on left turn or right turn)
(maybe later add some heave information?)

harness : 5 points
the 5th will prevent the harness to move up, this will give actual tension (vs movement)!

prefer larger harness 3" (vs only 2")

actuators:
RC servos 35kg.cm (5V to 8,3V)
PSU @7,3V

arduino:
code for 4 servos as I want to combine the harness and the gSeat pressure bladders also RC servo driven.

software:
FlyPT mover https://www.xsimulator.net/community/faq/flypt-mover.29/category

1607507030563.png


1607507067271.png


Here is a video showing step by step how I drive the 2 DOF belt with Fly PTmover:

The strength from 35kg.cm servo is enough.
Power up the servo, sit down, thight the belt as you wish, and start the sim. If you tight the belt before powering up the servos, they are loose and they could be pushed out of their range...

The only drawback, in my opinion is the noise of the servos: they are whinning...

here some infos gathered in this FAQ https://www.xsimulator.net/community/faq/harness-tensioner-simulation.361/

► shopping list
"HV high torque servo motor Robot servo 35kg RDS3235 Metal gear Coreless motor digital servo arduino servo for Robotic DIY"
17€
I chose 270° range
You'll need a dedicated power supply (5V slower to 7,4V fastest)

Speed: 0.13sec/60 degree at(5v)
0.12sec/60 degree at(6v)
0.11sec/60 degree at(7.4v)

Torque: 29kg.cm.at(5v) -1.9A
32kg.cm.at(6v) -2.1A
35kg.cm.at(7.4v) -2.3A

or stronger but unecessary in my opinion
60kgcm 24€
https://www.aliexpress.com/item/4000055027119.html

speed is voltage related
torque is current related!

1/ choose speed AKA voltage
2/ check the spec which gives you the current at chosen voltage
3/ buy your PSU ;-)

if you choose 7,4V PSU, verify it'll be able to deliver up to 2.3A in order to give full torque (add a BIG margin ;-) )
https://fr.aliexpress.com/item/32823922664.html
11€ 7V 10A


here is my ball bearing support (12€ for 2 supports)
they are made of:
P000 https://www.aliexpress.com/item/32833812473.html
Zinc Alloy Diameter Bore Ball Bearing Pillow Block Mounted Support
they allow a big static disalignment as the bearing is mounted like a joint articulation :)

and 200mm Ø10 linear shaft Cylinder Chrome Plated Liner Rods
https://www.aliexpress.com/item/4001294745058.html

roller-jpg.434449


ball-bearing-harness-2-jpg.439581


ball-bearing-harness-1-jpg.439582


► budget:
2x 17€ for 35 kg.cm servo
11€ for 7V 10A PSU
(maybe consider a 12V 10A PSU + an Adjustable Power Module Constant Current 5A)
12€ for bearing rollers
15€ any arduino with a USB port (an original to support the community or a clone)

50€ for a used 3" width 5 points harness
total = 120€ all included



arduino code (for up to 4 servos)
C++:
// Multi Direct
// -> 4 servos
// <255><LeftBelt><127><127><RightBelt>
// Rig : Bit output -> 8 bits
// avec inversion
// PT Mover envoie de 0 à 255 par axe

/*Mover = output "Binary" et "10bits"
Arduino = Byte Data[2]
Data[0] = Serial.read();
Data[1] = Serial.read();
result = (Data[0] * 256 + Data[1]);

OU

Mover = output "Binary" et "8bits"
Arduino = Byte Data
Data = Serial.read(); on obtient directement le résultat*/

#include <Servo.h>  // local library "Servo.h" vs library partagée <Servo.h>

const byte nbServos = 4;

// create servo objects to control any servo
Servo myServo[nbServos];
const byte servoPin[nbServos] = {2, 3, 4, 5};  // pins digitales (pas forcément ~pwm)
const byte inversion[nbServos] = {1, 1, 0, 0 }; // paramètre à changer si un servo part le mauvais sens
int OldSerialValue[nbServos] = {0, 0, 0, 0};
int NewSerialValue[nbServos] = {0, 0, 0, 0};

// servo span:
int servoHomeDegres[nbServos] = { 0, 0, 0, 0}; //sera mis à jour avec la mesure de pression initiale
int servoMaxDegres[nbServos] = { 90, 90, 90, 90}; // cuisseG, cuisseD, côtéG, côtéD
int servoPositionTarget[nbServos] = {0, 0, 0, 0};

const byte deadZone = 0;

// =======================================
// Variables for info received from serial
// =======================================
int bufferPrevious = 0;      // To hold previous read fom serial command
int bufferCurrent = 0;       // To hold current read fom serial command
int bufferCount = 0;         // To hold current position in bufferCommand array
// byte bufferCommand[2*nbServos] = {0};  // (*2 if 10 bits) To hold received info from serial
int bufferCommand[4] = {0};  // To hold received info from serial

void setup()
{
  Serial.begin(115200); // opens serial port at specified baud rate

  // attach the Servos to the pins
  for (byte i = 0; i < nbServos; i++) {
    // pinMode(servoPin[i], OUTPUT); // done within the library
    myServo[i].attach(servoPin[i]);  // attaches the servo on servoPin pin
    }
  // move the servos to signal startup
  MoveAllServos255toDegres(125); // mi-course
  delay(1000);
  // send all servos to home
  MoveAllServos255toDegres(0);
}

void loop()
{
  // SerialValues contain the last order received (if there is no newer received, the last is kept)

  if (Serial.available())
  {
    bufferPrevious = bufferCurrent; // Store previous byte
    bufferCurrent = Serial.read(); // Get the new byte
    bufferCommand[bufferCount] = bufferCurrent; // Put the new byte in the array
    bufferCount++; // Change to next position in the array
    if (bufferCurrent == 255) bufferCount = 0; // one 255 is the start of the position info
    if (bufferCount == nbServos) //si 8 bits, nbServos // si 10 bits nbServos*2
      //Having reach buffer count, means we have the new positions and that we can update the aimed position
    {
      for (byte i = 0; i < nbServos; i++) {
        NewSerialValue[i] = bufferCommand[i];
        //NewSerialValue[i]= (bufferCommand[i*2] * 256) + bufferCommand[i*2+1]; // si 10 bits
      }
      bufferCount = 0;
    }
  }
  // Update orders sent to motor driver
  for (byte i = 0; i < nbServos; i++) {
    if (abs(OldSerialValue[i] - NewSerialValue[i]) > deadZone) {
      if (inversion[i] == 1)
      {
        envoiServoConsigne255toDegres(i, (255 - NewSerialValue[i]));
      }
      else
      {
        envoiServoConsigne255toDegres(i, NewSerialValue[i]);
      }
      OldSerialValue[i] = NewSerialValue[i];
    }
  }
}

void envoiServoConsigne255toDegres(byte servoID, int val )
{
  byte targetDegres;
  val = constrain(val, 0, 255); // constrain coupe au dessus et en dessous : écrêtage et pas mise à l'échelle (comme map)
  // sécurité pour éviter les cas où Simtools enverrait du négatif ou au-delà de 255
  targetDegres = map(val, 0, 255, servoHomeDegres[servoID], servoMaxDegres[servoID]);
  //  map(value, fromLow, fromHigh, toLow, toHigh)
  myServo[servoID].write(targetDegres);              // tell servo to go to position in variable : in steps of 1 degree
  // servo.write(angle)  -> angle: the value to write to the servo, from 0 to 180
}

void MoveAllServos255toDegres( int target)
{
  // send all servos to home
  for (byte i = 0; i < nbServos; i++) {
    envoiServoConsigne255toDegres(i, target);
  }
}

here is the setup:
setup 2DOF harness.png


and the file itself: replace .txt extension by .Mover extension
 

Attachments

  • 4Dof_Belt_255_multiDirect_v6bALPHA.txt
    94.9 KB · Views: 454
Last edited:
Technically the current power supply should work for this still but may require changing as well
Overheating suggest that a motor is being driven against a hard limit. 60kg seems a lot of force.. Being able to monitor power supply current is one bonus advantage for using a current-limiting regulator to drive super-capacitor bypassed servos:
Supercaps are typically rated at 2.8V; 6-cap commodity units could nominally supply16.8V,
where anything much over 8V is overkill for hobby servos.
Capacitors in series require balancing circuitry; since they are never perfectly matched,
some might otherwise exceed rated potentials before others are charged. A motivation for buying
a commodity 16V assembly was lower cost than separate capacitors and balance circuits.
Since servos without supercaps rarely drew over 2 Amps, half the supercaps were deemed sufficient,
so their circuit board was cut in half on a bandsaw:
View attachment 442519
A square cut from a corrugated Amazon carton was hot-glued to one side of 3 supercaps,
then regulator and Blue Pill boards were hot-glued to that square; all were then wired together:
View attachment 442520
The Blue Pill was located for easy access to its reset button.
Servos had previously been supplied with 5V; with 8V they now are not only more responsive but also quieter.
Those LED digits can report current, which simplifies setting max tension, since that is against a physical limit.
the difference between max tension settings 90 and 91 is the difference between 0.46 and 0.52A,
while there was negligible current difference between settings 89 and 90; 92 bumps current to 0.98A.
Servos draw 0.05A untensioned.
The regulator heat sink warms slightly while initially charging supercaps, but cools thereafter.
With regulator limiting current to 4.5A, supercaps reach full charge a few seconds after PC completes booting into Windows.

After a dozen figure 8 laps and fiddling decel and delta yaw gains (respectively to 178 and 72),
harness tensions began to feel almost natural, offering a much better sense of cornering Gs than from ShakeIt haptics.
 
Upvote 0
SimHub release 7.3.14 adds lateral acceleration support.
My SimHub CUSTOM SERIAL DEVICE harness tensioning profile has been simplified and updated to exploit SimHub 7.3.14 accelerations, instead of deriving those from speed and yaw changes.
Here is updated message Javascript from that profile:
JavaScript:
// G-forces from SimHub properties
var Gy = - $prop('GameRawData.Physics.AccG01');    // lateral (yaw) acceleration
var Gs = - $prop('GameRawData.Physics.AccG03');    // deceleration
Gy *= $prop('Settings.yaw_gain');
Gs *= $prop('Settings.decel_gain');
if (0 > Gs)
  Gs = 0;                // non-negative deceleration

// convert speed and yaw changes to left and right tension values
// turning right should increase right harness tension (body pushed left)
var r = Math.sqrt(Gs*Gs + Gy*Gy);
var l = Gs + Gs - r;
if (0 > Gy) {
  var t = r;    // negative Gy increases left tension
  r = l;
  l = t;
}

// Low-pass IIR filtering of left and right tension values
if (null == root["lb4"]) {
  root["rb4"] = r;  root["lb4"] = l;    // initialize
}
var rb4 = root["rb4"];
var lb4 = root["lb4"]; // previously filtered values
var tc = 1 + $prop('Settings.smooth');
rb4 += (r - rb4) / tc;
lb4 += (l - lb4) / tc;
root["lb4"] = lb4;
root["rb4"] = rb4;

l = lb4; r = rb4; // filtered tensions;  comment out for unfiltered (or set Settings.smooth = 0)
var tmax = $prop('Settings.tmax') & 126;
if (l > tmax)
  l = tmax;
else if (l < 2)
  l = 2;
l &= 0x7E;      // left lsb is 0
tmax |= 1;
if (r > tmax)
  r = tmax;
else if (r < 3)
  r = 3;
r |= 1;         // right lsb is 1

if ($prop('Settings.max_test') || $prop('Settings.TestOffsets')) {
  // disable normal message output
  // slider changes will provoke first message outputs
  root["lb4"] = root["rb4"] = 0;
} else {
//* servo control output
  var ls = String.fromCharCode(l);      // tension control characters
  var rs = String.fromCharCode(r);
//*/

/* gnuplot output **************************************
  var s = $prop('SpeedMph');
  var ls = l.toString();
  var rs = r.toString();
  var ss = s.toString();
  var Gys = Gy.toString();
  var Gss = Gs.toString();
  rs = ls.concat('\t',rs,'\r\n');  // gnuplot columns
  ls = ss.concat('\t');
*/
  return ls.concat(rs);
}
Acceleration evaluations was done here for related tactile effects:
Comparing my Javascript proxies to SimHub's GameRawData.Physics properties:
View attachment 468042
View attachment 468043
They are similar enough to justify code changes.

For the curious, above Gnuplots were generated using this Javascript Custom serial device update message:
JavaScript:
// Initialize history
var s = $prop('SpeedMph');
var y = $prop('OrientationYaw');
if(null == root["speed"]) {
   root["speed"] = s;
   root["delta_s"] = 0.2;
   root["yaw"] = y;
   root["delta_y"] = 0.2;
}

var ds = root["speed"] - s;        // negative of speed change
var dy = y - root["yaw"];

if (Math.abs(dy) > 180) {  // yaw went +/- 180
  if (Math.abs(root["yaw"]) > 150)
    dy = -(y + root["yaw"]);
}
var YawGain = 72;
var DecelGain = 178;
dy *= YawGain;
ds *= DecelGain;
var ms = 1;
var my = 1;  // sample interval factors
var Gy = root["delta_y"];
var Gs = root["delta_s"];

// check for delta spikes from missed samples
var t = 2;                                   // non-linear spike threshold
if (Math.abs(Gy * dy) > t && Math.abs(dy) > Math.abs(1.8 * Gy))
  dy /= (my = 2);      // compensate sampling artifacts
if (Math.abs(Gs * ds)  > t && Math.abs(ds) > 1.8 * Math.abs(Gs))
  ds /= (ms = 2);      // compensate sampling artifacts

// low-pass IIR filters
var tc = 3;  // lowpass IIR
Gs += ms * (ds - Gs) / tc;
Gy += my * (dy - Gy) / tc;
root["delta_y"] = Gy;
root["delta_s"] = Gs;
// store unfiltered values for next increment
root["speed"] = s;
root["yaw"] = y;


// SimHub
var accel = $prop('GlobalAccelerationG');
var acc01 = $prop('GameRawData.Physics.AccG01');
var acc02 = $prop('GameRawData.Physics.AccG02');
var acc03 = $prop('GameRawData.Physics.AccG03');
var acs = accel.toString();
var a1s = acc01.toString();
var a2s = acc02.toString();
var a3s = acc03.toString();
var dys = Gy.toString();
var dss = Gs.toString();
return dys.concat('\t',dss,'\t',acs,'\t',a1s,'\t',a2s,'\t',a3s,'\r\n');
... which was echoed back to the Incoming serial data window
using this Arduino sketch.

Tuning this updated profile is as before, except for updated gain slider limits:
tension.gif

  • click "Test untension positions" box and adjust Left and Right untensioned sliders for longest harness straps: pulling on them should show no increase in power supply current.
  • click "test max tension" box and adjust max tension slider for shortest harness straps that provoke no increase in power supply current when NOT pulling on straps. This is to ensure that servos cannot overcurrent by driving against mechanical limits.
  • unclick both boxes for normal operation.
  • use lowest smoothing slider value that allows servos to not jiggle unnecessarily
  • apply largest gain slider values that avoid excessive clipping for high cornering and braking events.
Additional custom serial documentation is on GitHub, as is corresponding Arduino documentation and sketch to drive the hobby servos.

Motivation to update this DIY harness tensioner after @Wotever releases his version would likely diminish...
 
Last edited:
Upvote 0
Video update from @Wotever:
  • hard to be certain from audio mix, but his actuators may be as noisy as hobby servos
  • those actuators appear to provide both more force and range of motion than hobby servos
  • current implementation seemingly lacks rollers or pulleys to convert strap down pulls to rear forces.
  • jerks during upshifts seem IMO over the top..
 
Last edited:
Upvote 0
@RacingMat any suggestions why the lateral G only works going around left hand corners (right belt tightens, left loosens)? Aside from that my setup is all complete!
image.png


I'm using the stronger motors and they feel pretty good
image_55415491.JPG


I would actually prefer the strength maybe 10% more but I can't complain, I found it worth the upgrade over 35kg force motors and the included brackets allowed for hard mounting which I preferred over the suspended orientation as it dissipates the heat better through the structure.

Also if anyone has a more elegant suggestion to replace the cable tie I would be interested to hear your suggestions, I was thinking perhaps some sort of carbine clip but I am not so sure...
 
Last edited:
Upvote 0
Hi @deandsfsdfewsad

I added my setup screenshot and mover file in the first post ;)
Thanks @RacingMat sadly my motors burnt out, I am not sure whether I drove them too hard with the PSU current being too high or whether they were pushed outside of their working angular window. I dismantled the motors and the gearing mechanism seems fine the only issue is the motor itself sadly -> this has been a fun project but I can't seem to be sure of the root cause of the failure so I don't think it's wise to buy more as the same thing will happen again for the 3rd time, have you experienced any motors burning out? When I do up the belts I make them very tight so I don't know if the force to overcome my body placed too much stress on the small motors...
 
Upvote 0
your body is too strong!! :confused:
that's too bad...
both motors exactly at the same time?? or not?

Current is related to motor's exerted force: nothing to check with PSU current. It only delivers what is asked by the motor. But yes if the motor is stalling and if the PSU can deliver the current needed, current spikes... => heat, burning, smell...
A voltage spike could burn the motor or the board (without melting it). Can you check your PSU voltage?

Adding a spring would limit the maximum stress: it's a kind of mechanical fuse that would protect your motor.

Some people have melted their servos (too much heat) but it's not your case. :thumbsdown:
 
Upvote 0
your body is too strong!! :confused:
that's too bad...
both motors exactly at the same time?? or not?

Current is related to motor's exerted force: nothing to check with PSU current. It only delivers what is asked by the motor. But yes if the motor is stalling and if the PSU can deliver the current needed, current spikes... => heat, burning, smell...
A voltage spike could burn the motor or the board (without melting it). Can you check your PSU voltage?

Adding a spring would limit the maximum stress: it's a kind of mechanical fuse that would protect your motor.

Some people have melted their servos (too much heat) but it's not your case. :thumbsdown:
haha I'm not like superman or strong at all but I have found on my dd I like stronger ffb, I like strong braking forces for my pedals and I liked the strength of the higher motors giving the perceived forces I assumed since I tightened the belts a lot compared to others who might have it more 'comfortable' it could lead to them burning out

The weaker motors 35kg and the 60kg ones both went and pretty much at the same time after 2 laps Imola, from the first set it was the left-hand side and the second set the right-hand side went first, the second set of motors were going well for about 20mins going around Mangy-Cours.

The idea of the spring system is not a bad idea, I suspect the motor stalling likely caused the damage, they were quite hot but not when they failed during testing without any load they would get very hot to the point where I could not touch them. I could also smell the motors occasionally or I believed that I could, it wasn't a strong smell
 
Upvote 0
decent psu to power my cheap servos
An XL4015 5 Ampere buck regulator to step down a generic 12V power dongle for hobby servos has been entirely satisfactory for me. It allows precisely matching servos' voltage and subsequently monitoring current draw:
RDS3235 servos, rated 5 - 8.4VDC, were delivered from eBay on the same day as 5A constant current voltage regulator from Amazon.
First things first: button left of LED digits turns LEDs (but not power) off/on:
View attachment 438828
Right pushbutton controls display mode: Vin, Vout, Aout, Pout, cycle.
Pots were received set fully clockwise, turning counterclockwise reduces values.
Left pot controls Vout, right pot controls Aout. Upper blue LED chip illuminates while powered.
Another LED chip just left of that illuminates when current limited. LED chip right of digits
illuminates when displaying Vout; LED chip left of digits illuminates when displaying Vin.
I tested it using a halogen automotive headlamp load:
View attachment 438833
Regulator heatsink gets fairly warm fairly soon with this load, powered from 12VDC 5A supply.
 
Last edited:
Upvote 0
I wanted this setup for that reason so that I could increase the range of usage
By fully tightening harnesses with servos fully extended, adequate tension is felt when servo arms fully contract with neither excessive current draw nor servo heating. Less noise is heard with servos suspended between strap segments instead of bolted to frame members.
Adding springs in series would reduce effectiveness of servo motions.
 
Last edited:
Upvote 0
Comparing how your servos were mounted with mine and @RacingMat, yours are working at a mechanical disadvantage.
good point, I didn't notice they were reversed :geek:
indeed the lever is (too) much longer in this configuration.

@deandsfsdfewsad : have a look at stepper motors? that's what Wotever used.

Adding springs in series would reduce effectiveness of servo motions.
yes, indeed but this could protect the servos (for initial testing a least).
a carefully calibrated spring (stiffness and extension vs servo's force and excursion) could be quite effective.
 
Upvote 0
a carefully calibrated spring (stiffness and extension vs servo's force and excursion)
A human torso effectively represent those springs for brackets included with RDS3235 servos, which provide 4-5cm strap travel and can exert over 20kg at maximum retraction, for which one of my servos draws around 1.3A and the other 0.9A when driven with 8.0V. I do not know why the difference, but that higher current servo certainly heats more quickly. Tuned to exert that only for peak braking and cornering, servos are not particularly warm during normal operation.

have a look at stepper motors
IMO, employing motors with gearing capable of injurious forces is bad.
Few of us are qualified for safety-critical software development.
 
Upvote 0
Back
Top