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:
Ebay delivered RDS3235 servos, rated 5 - 8.4VDC, on the same day as
XL4015 5A constant current voltage regulator from Amazon.
First: button left of LED digits turns LEDs (but not power) off/on:
controls.jpg

Right pushbutton controls display: 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:
load.jpg

Regulator heatsink gets fairly warm fairly soon with this load,
powered from 12VDC 5A supply.
 
Last edited:
Upvote 0
US$4 USB-powered servo tester (from microscope stage controller)
runs RDS3235 servos to CCW limits for bracket installation,
but Arduino can drive servos farther than does this servo tester.
CCWservos.jpg

Motor wires exiting servo body interfere with bracket motion.
Suspending servos between harness straps reduces gear noise:
 
Last edited:
Upvote 0
Last edited:
Upvote 0
do you have a static rig?

Yes, very static;
a wheel stand and harness frame on Aeron swivel office chair:
extruded.jpg


A 4 point harness avoids piercing ShakeSeat and mesh seat
for submarine straps.
Tightening waist straps as for real track driving
sufficiently resists shoulder strap servo tensioning.
I'll ask on racingfr.net if someone uses Simhub + servo RC
Thanks, Arduino has libraries for servo and serial,
and Blue Pill examples already existed.
Sorting SimHub Custom Serial may be harder.
 
Last edited:
Upvote 0
Years ago, the Simhub developper found problems "due to the noise, calibration issues, and low speed of those servos".
I have to disagree because with FlyPT mover it works really fine.

I keep searching about Simhub and RC servos.

I am still researching integration with SimHub Custom serial devices support
with this plugin, the only acceleration available is [GlobalAccelerationG] ...

I've seen this more straightforward? https://github.com/SHWotever/SimHub/wiki/Custom-Arduino-hardware-support
 
Last edited:
Upvote 0
Years ago, the Simhub developer found problems
"due to the noise, calibration issues, and low speed of those servos".
I have to disagree because with FlyPT mover it works really fine.
I had also read that; RC servos are indeed relatively noisy,
but you and I ( at least) are prepared to deal with / accept that..
Another issue:
- some games' telemetry are much noisier than others.
AccuForce developers write about filtering such signals.

I keep searching about Simhub and RC servos.
No need for RC servo specifics; a simple Arduino "sketch"
needs only braking and lateral accelerations from SimHub.


That is about SimHub-generated Arduino code,
rejected because:
  • supports older, slower and expensive Arduinos
  • generate unnecessarily complex code for this application.
Custom Serial device support reduces SimHub-specific task:
- sending appropriate data to a specified COM port,
- a USB-attached STM32 Blue Pill that drives servos
- (still in the process of being documented.)
 
Last edited:
Upvote 0
Upvote 0
Isn´t it better if the servos would be attached to some rigid part
or frame so they don´t "pivot" and translate all the rotation to tension the harness?
No. rigid attachment conducts vibrations into structures,
increasing audible noise.
Servo movements adequately tension straps.

SimHub Custom serial device settings progress is slow:
SimHubCustomSerial.gif

Here are SimHub settings which control servos.
Sliders adjust maximum tensions,
when [] Test tight is checked.
Other gain sliders to tune braking and lateral forces..
  • Servos require less than 127 degree steps for belt tensioning;
    single byte commands suffice.
    • that massively simplifies communication protocol
  • SimHub supports NCalc and JavaScript
    • but NCalc does not (easily) support writing single ASCII characters for decimal values
  • Likely parameters for calculating servo movements:
    • GameData.GlobalAccelerationG
    • GameData.OrientationYaw
  • SimHub documentation is scant
  • discord is IMO an inefficient communication tool...
 
Last edited:
Upvote 0
A scheme for sorting candidate telemetry parameters:
SimHub supports:
  • replay of recorded sessions
  • logging of incoming serial data.
    • this works, but only for printable ASCII..
Consequently:
 
Last edited:
Upvote 0
  • Likely parameters for calculating servo movements:
    • GameData.GlobalAccelerationG
    • GameData.OrientationYaw
  • documentation seems scarce and discord an inefficient communication tool...
I saw only GlobalAccelerationG and thought it was toasted for a 2DOF harness... but with OrientationYAw that could do the job: you had a good idea!

I taggued the developper but no answer yet...
 
Upvote 0
you had a good idea!
There is a saying, "even a blind hog finds an acorn once in awhile".
There are also GameRawData.Physics.AccG0[1-3] properties,
but their generality appears uncertain,
and testing will employ only Assetto Corsa.

8 Mar 2024 update:
SimHub now has 3D acceleration properties for most games.
Compared with AC/ACC-specific properties:
Code:
AccelerationSway = 9.8 * GameRawData.Physics.AccG01;
AccelerationHeave = 9.8 * GameRawData.Physics.AccG02;
GlobalAccelerationG = 9.8 * GameRawData.Physics.AccG03;

I have some (mostly unpleasant) previous JavaScript experience
from before debugging facilities were generally available.
Most SimHub scripts employ NCalc, which is new and strange to me.

I taggued the developper but no answer yet
Frankly, some discord responses puzzle more than help.
SimHub is quite extensive and presumably keeps Wotever busy.
Software architects may forget that what seems obvious
can be mysterious to others.
Perl books by its creator, Larry Wall, are in that category.

Lacking motivation to generalize SimHub G-Harness
beyond STM32 and Assetto Corsa,
its GitHub documentation and source code comments
should enable others to support other devices and games.
This Arduino sketch is specific to Blue Pill
mainly by pin assignments and timeout count.
 
Last edited:
Upvote 0
playback session, while separately logging candidate parameters echoed to input
Second attempt, using this custom serial message formula
to print 3 columns of synchronized telemetry:
format([OrientationYaw],0)+'\t'+format([SpeedMph],0)+'\t'+format([GlobalAccelerationG],0)+'\r\n'

.. since gnuplot likes columns:
raw_accel.gif

Now, I am far from the smoothest of drivers,
and this was from a less than great lap,
but acceleration data seems pretty noisy.
Servos probably cannot respond so quickly,
which would smooth haptics,
but those signals want LOTs of software filtering.
I have no idea what sort of units are reported by
GlobalAccelerationG ($3), probably not Gs..
Values to Arduino need rescaling 0 - 127 to fit ASCII characters.
(8 Mar 2024: SimHub Custom Serial now supports 8-bit values)

SpeedMPH seemed more credible,
deltas ($2) are nearly so noisy as GlobalAccelerationG

OrientationYaw presumably reports degrees heading;
sudden transitions between -180 and +180 are clues.

Changes between Yaw samples ($1) should be lateral-acceleration-related..

gnuplot calculated speed deltas in comparison to rescaled GlobalAccelerationG
.. and delta_v(SpeedMPH) basically overlays rescaled GlobalAccelerationG.
Gnuplot script:
Code:
set size ratio 0.4
set yrange [-2.0:3.0]
d30(x) = (x / 30)
delta_v(x) = (vD = x - old_v, old_v = x, vD)
old_v = NaN
plot 'YawSpeedAccel.txt' using 0:(d30($3)) w l
replot 'YawSpeedAccel.txt' using 0:(delta_v($2)) w l
replot 'YawSpeedAccel.txt' using 0:(delta_v($1)) w l
Given a figure-8 track,
yaw deltas seem credible proxies for lateral acceleration.
Most delta_v "noise" was SimHub artifacts from
sampling rate somewhat slower than Assetto Corsa updates;
deltas occasionally represent 3 game samples, rather than 2.
That was addressed by a non-linear filter
Code:
if (a current delta value is roughly twice the previous)
     then (divide it by 2)...
Alternatively, perhaps normalize speed and yaw deltas by SystemInfoPlugin.Uptime deltas.
 
Last edited:
Upvote 0
Back
Top