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:
More fun and games, sorting SimHub custom serial device conventions and reviving weak Javascript foo..

Arduino sketch echoing hex values for each byte confirms:
- SimHub String.fromCharCode()
limits sent byte values to [0-127].
- Values over 127 are replaced by 63 / '?' / 0x3F.
Fortunately, unprintable ASCII values below 127 are sent.
  • values 0 and 1 are reserved for adjusting harness tension:
JavascriptSliders.gif

  • first 2 lines of Javascript capture slider values
    as l and r
  • next 2 lines set left and right tension control codes
  • next 4 lines convert tensioner codes and values to characters
  • last line returns that string of characters as the message to send
  • to do: Test tension checkbox
    should disable game telemetry
    and send left and right maximum servo values,
    for sliders to adjust comfortable tension.
Javascript is arguably better for boiling an ocean than stirring a teacup.
 
Last edited:
Upvote 0
weak Javascript foo
SimHub custom serial settings for "tension" unit testing:
tension.gif

Unit testing variables were echoed, captured and plotted,
rather than driving servos.
Here are plots with full smoothing:
filtered.gif

... and plots with smoothing sliders set to 1, disabling filters:
unfiltered.gif

left and right signal separation increases with increasing delta yaw gain

For actually driving harness servos, these settings worked:
  • Left tension = 62; Right tension = 64
    • these set untensioned positions, varying with hardware
    • another slider for max tension was wanted; 92 works
  • decel gain = 209; delta yaw gain = 50
  • delta yaw smoothing = decel smoothing = 10
    • a single 1 - 20 smoothing slider suffices
These apply as much tension as I care to experience.
Much incriminating debris is on GitHub
 
Last edited:
Upvote 0
Supercaps finally arrived; jury-rigged power supply will get dismantled
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:
halved.jpg

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:
glued.jpg

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.
 
Last edited:
Upvote 0
Javascript for deriving a lateral acceleration proxy from yaw changes seems satisfactory
and might helpfully supplement WHEELS SLIP haptic signals e.g. from ShakeIt to ShakeSeat.
Duplicating that Javascript seems wasteful, and ShakeIt supports Javascript custom effects,
which can export values as properties; moving G-force proxy Javascripting to ShakeIt custom effects
and exporting as properties would allow them to be multi-purposed..

As is often the case, using ShakeIt to generate acceleration proxy properties turned out to be less ideal than hoped;
ShakeIt properties are constrained to range [0:100], requiring 50 as the new 0 for accelerations.
This precludes use of ShakeIt Gamma other than 0 or Gain other than 100, for which 50 would no longer correspond to 0.

Another confounding issue: previous Javascript was written for SimHub 7.0.7, which exhibited these delta_v artifacts:
.. which were easily mitigated. With SimHub 7.3.4, delta s and delta y artifacts are more subtle:
proxy.gif

... and harder to sort, since much less than 2x normal signals.
Gs and Gy are IIR-lowpass-filtered from delta s and delta y, adding some lag to signals.
Meanwhile, left and right tension control signals derived from those filtered Gs and Gy are still somewhat noisy.
IIR filtering of these control signals wants investigating...
 
Last edited:
Upvote 0
Another video, this time live sim driving:
This may be more useful for judging servo noise relative to Assetto Corsa audio,
rather than judging harness tensioning that feels fine,
despite less than dramatically visible strap movements,
which holds true even for the high-priced spread:
There isn’t a whole lot to see when the belts are working

VR with headphones further attenuates servo noise.
 
Last edited:
Upvote 0
Thanks again for all the help and support!;

This is the setup in an F1 style rig; Seatbelt will arrive within short term; Have to check how it functions (small rollers vs bending of belt); In worst case I have to adjust and increase the diameter and/or remove one roller... I'll let you know;...

I'll switch from the Arduino Uno to the STM32 to prevent the struggle with adapting the software (I'm not capable of doing...)....


1.jpg
 

Attachments

  • 3.jpg
    3.jpg
    529.4 KB · Views: 148
Last edited:
Upvote 0
I'll switch from the Arduino Uno to the STM32 to prevent the struggle with adapting the software
(I'm not capable of doing...)....
STM32 blue pill and ST-Link together should cost less than an Arduino, and no shield is needed.

You were reluctant to e.g. double-sided sticky foam tape the fairleads to the back of the racing seat..?
Or the seat (like mine) lacks shoulder harness ports.
 
Upvote 0
At the moment I have an Audino on delivery
OK, Arduino UNO R3 is capable of directly driving hobby servos.
Substituting appropriate pin numbers in this sketch
should work with this SimHub custom serial device
Powering servos from higher than 5V
improves responsiveness and seemingly reduces noise.
 
Last edited:
Upvote 0
In the latest (PT Actuator front traction loss) Sim Racing Garage video review,
Barry brags on harness tensioning based on surge, rather than independently.
In the video, lap belts appear to be tensioned more than shoulders,
which I do not recall in real cars, probably because I was usually bracing with my left foot.
Based solely on surge, Barry cannot get differential shoulder harness tensioning in corners,
which seems a shame, given relative costs of motion platform and independent harness tensioning.
 
Upvote 0
In the latest (PT Actuator front traction loss) Sim Racing Garage video review,
Barry brags on harness tensioning based on surge, rather than independently.
In the video, lap belts appear to be tensioned more than shoulders,
which I do not recall in real cars, probably because I was usually bracing with my left foot.
Based solely on surge, Barry cannot get differential shoulder harness tensioning in corners,
which seems a shame, given relative costs of motion platform and independent harness tensioning.
Would he not get that during sideslip and yaw though from the traction loss system? I am still waiting for my bits to arrive, should be a few more weeks
 
Upvote 0
Would he not get that during sideslip and yaw though from the traction loss system? I am still waiting for my bits to arrive, should be a few more weeks
Sustained corner loads, such as provided by 2DOF harness, is not possible by motion platform,
and his harness is evidently attached for actuation only by surge. Combining surge, yaw and traction loss could change harness length more than 6 inches, which could injure.

Yaw changes by motion platform not matched by harness tension would IMO seem unnatural.
 
Upvote 0
Not at UNO user; don't know why 2 red wires from the power supply to it,
but there certainly should be (black) ground wire connections
between UNO, power supply and servos.
IMG_1370.jpg

I simplified it and moved the ground to the supply input ground (red wire as this is all I have for now) but still no dice, I don't know the pinout for the Arduino. I tried the following to see if I could get it to work through USB to verify the motors work/ Arduino but nothing:
1616337348654.png
IMG_1370.jpg
1616337348654.png
 
Upvote 0
Back
Top