multi-character control for SimHub Custom serial devices

blekenbleu

SimHub+Arduino hacker
Premium
For the specific case of controlling hobby servos for harness tensioning by STM32 "blue pill" modules,
single (7-bit) character controls were implemented here:
Human vision can discern roughly 100 just noticeable differences in print densities and CRT display intensities, which made 8-bit digital intensity values, with appropriate non-linearities, suitable for rendering smooth tone gradients. On the other hand, for simulating acceleration forces by harness tensioning, slowly and smoothly incrementing tensions is not compelling. So long as e.g. felt differences between tension steps 4 and 12 are roughly equal to differences between steps 8 and 16, I see no value in rendering an undetected tension increment between any two adjacent steps.
A custom serial Javascript SimHub message was hacked to test for noticeable tension increments:
JavaScript:
// Repurpose gain sliders for just noticeable tension steps
// servo range: 2 to 90
if ($prop('Settings.max_test') || $prop('Settings.TestOffsets'))
  return;
var d = new Date();
var dg = Math.max($prop('Settings.decel_gain'),2);    // base tension
var yg = $prop('Settings.yaw_gain');                // tension increment
if (null == root["odg"]) {
  root["odg"] = dg;
  root["oyg"] = yg;
}
if (dg != root["odg"] || yg != root["oyg"] || null == root["Time"]) {
  root["Time"] = d.getTime();
  root["odg"] = dg;
  root["oyg"] = yg;
}
var s = root["Time"];
var t = d.getTime() - s;
// four tension difference pulses over 3.5 seconds
if (3500 < t)
  d = 2;            // timeout: release tension
else if (3000 < t)
  d = dg + yg;        // base + difference tensions
else if (2500 < t)
  d = dg;            // base tension
else if (2000 < t)
  d = dg + yg;
else if (1500 < t)
  d = dg;
else if (1000 < t)
  d = dg + yg;
else if (500 < t)
  d = dg;
else
  d = dg + yg;    // may be even or odd
var o = 1 + d;  // the other one
//return d.toString()+"\t"+o.toString();
return String.fromCharCode(d)+String.fromCharCode(o);
.. resulting in this "linearization" of perceived tension increments:
View attachment 476039
.. where servo values 2 to 90 map to percent rotation values 0 to 100 in the previous reply.
With maximum servo tension about so much as wanted without being distracting,
only 21 tension increments could be perceived under nearly ideal conditions,
including not being distracted by trying to drive at the same time.
Slightly coarsening steps to 16 allows encoding tensions to 4 bits,
leaving 3 of 7 ASCII bits for addressing 7 servos and one address reserved
for downloading a lookup table to convert those 4 tension bits back to servo values.

Applying swingarm geometry to servo values results in this plot
for equalizing perceived tension increments with belt movement:
View attachment 476048
.. but a more general implementation with more value precision (reducing a need for lookup tables)
and more addressable channels (pins) is wanted (at least, by @sierses and me),
involving multi-character USB message support for more than 7 bits of information,
while minimizing overhead in data traffic, SimHub message and Arduino code complexity.

Since USB, unlike e.g. TCP/IP, lacks inherent message integrity,
a mechanism is wanted that minimizes bad consequences for characters lost e.g. to EMI.
Arduino sketch and matching SimHub profiles on Github
 
Last edited:
multi-character USB message support for more than 7 bits of information
Practically, we anticipate wanting no more than 31 channels
with data precision of 128 or fewer increments, which amounts to 5+7 = 12 bits.
At least when using JavaScript, SimHub Custom Serial devices USB messages
are constrained to 7 bits (ASCII characters) values per byte sent.
Allocating 1 bit in each character to identifying
whether it is first or second of a character pair leaves 12 bits available.
This most-significant-bit on first character ploy is used by MIDI....

One of 32 possible channels will be reserved for special purposes,
such as changing debug feedback.
Depending on the application,
more than one channel may be associated with a single STM32 peripheral
when more than one parameter wants updating in real time.
 
Last edited:
Upvote 0

bit allocations​

Allocating 1 bit in each character to identifying
whether it is first or second of a character pair leaves 12 bits available.
To minimize bit masking and shifting, first (most significant) bit identifies which of pair characters:
  • 1 = first of paired characters
  • single most significant of 7-bit value
  • 5-bit channel address

  • 0 = second of paired characters
  • 6 least significant bits of 7-bit value
It is possible that, when an even number of consecutive USB characters are lost,
STM32 could mistakenly associate a first character with the wrong second character.
However, STM32 will maintain a state machine so that it will ignore
a second character received when expecting a first character.

For the reserved special channel, a 1 set in the first character's
6th most-significant position will force STM32 firmware to reset.
This would be the only valid single character message, and
allows for only 64 other unique special channel commands,
based on bits available in the second character.
Some of those 6-bit values in special command second characters
may require subsequent characters to be processed
e.g. as a table of values to be downloaded.
Validation of such table downloads might be by trailing checksum.
 
Last edited:
Upvote 0
single (7-bit) character controls were implemented
Custom serial JavaScript for proposed 2-character control will be
more similar to earlier 6-bit data profiles than current 4-bit LUT profile.
Sorting the plethora of profiles provoked some documentation of same.
Earlier implementations supported only 2 Arduino servos,
selectable by the least significant of 7 bits.
7-bit values 0 and 1 were special-cased such that
an immediately following character is their zero tension setting.
Given that precedent, channels 0 and 1 will be allocated
to zero tension position settings for channels 2 and 3, respectively.
A prototype Custom serial profile was hacked,
starting with the current profile with the latest sliders,
then borrowing messages from the most recent 6-bit profile,

JavaScript:
// 2-character harness tension initializer
var Lof = $prop('Settings.LeftOffset');
var Rof = $prop('Settings.RightOffset');
var t = ($prop('Settings.max_test')) ? $prop('Settings.tmax') : 0;    // max tension
var step = [0x40 | ((0x40 & Lof)>>1)];    // warn Arduino of Left offset first char
step[2] = 0x41 | ((0x40 & Rof)>>1);        // first char of Right offset
step[1] = 0x3F & Lof;    // 6 lsb
step[3] = 0x3F & Rof;
step[4] = 0x42 | ((0x40 & t)>>1);        // harness servo channels
step[6] = 0x43 | ((0x40 & t)>>1);
step[5] = 0x3F & t;
step[7] = 0x3F & t;
// send servo offsets and update servo positions using those offsets
// return step.toString(); // would return a comma-separated string of numeric values
var str = String.fromCharCode.apply(null,step);    // ASCII code for each step[] value
// return str.length;    // 8 characters
return str;
 
Last edited:
Upvote 0

First (known) customer!​

adding more should be relatively trivial.
A G seat seems a likely sim racing use for more than a pair of hobby servos,
and air wedges offer an expedient implementation:

@sierses extended 2-character sketch and SimHub profile from 2 to 12 servos:
20210929_003428.jpg

.... but this exposed a SimHub limitation: Custom Serial messages of more than about 120 characters cause SimHub to hang, along with that COM port.
Working around such issues wants a consolidated GitHub repository:
  • matching Arduino sketch and SimHub profiles
  • a single sketch for both STM32 Blue Pill and Black Pill with only a single change
  • number of servos configurable from the SimHub profile
 
Last edited:
Upvote 0

Human factors​

Accomplishing basic function took less than a week of part-time floundering.
A harder problem, as @Wotever discovered for his own DIY Belt tensionner,
is SimHub Custom Serial user interface limitations,
which he eventually worked around by creating a dedicated Belt tensionner plugin.

Here is the Oct 11, 20212 state of harness2char Custom Serial multi-servo UI:
harness1.gif

harness2.gif

First, to explain/describe what is there:
  • First slide selects page and parameters to tune
  • Only 2 servos can be tuned
  • Page 1 sliders tune associated telemetry signals
    • Info slider selects diagnostics to log in Incoming serial data
  • Page 2 tests/changes 3 channel parameters, a pair at a time
    • The Test/update checkbox may potentially destroy servos.
Now, to ramble about what is addressed:
  • Display arrays of parameter values for each servo.
    • Move parameter sliders to select servo current values,
      while avoiding conflicts among settings (hence, percentages)
    • The only known feedback mechanism is sketch Incoming serial data
  • Instantly interactive controls:
    • servos are easily damaged by prolonged overloads.
    • physical servo linkage variations want individual limit tunings,
      ideally while monitoring current consumption, e.g.
      controls-jpg.438828
  • Under consideration: semi-automatic gain adjustment
    • If/when the sketch detects a value that would move a servo
      beyond its specified limit, it could not only clamp values and warn
      but also change gain factor to accommodate that value.
      The profile would need a special command to set that mode,
      which would return a string of current gain values.
 
Last edited:
Upvote 0
Coordination among SimHub Custom Serial messages
is only by properties (which it can only read), and
settings from GUI elements to the left, which again are read-only.
Serial traffic is write-only, so no echoing serial port data.
Consequently, any coordination must be within a single message
or by whatever is implicit in handling properties and settings,
which results in JavaScript for that one message becoming relatively complex.

As with ShakeIt, profile file structure does not lend itself to use
by generic software tools e.g. for differencing etc. This in turn encourages
maintaining separate JavaScript source files for easier software engineering.
Unless one also maintains a separate compatible JavaScript engine,
debugging requires copying that JavaScript source back into a profile
loaded and running in SimHub,
making development less efficient than for e,g. Arduino.

All of which is a preamble for what initially was expected
to be a relatively modest refactoring to ensure any parameter changes
with a test box checked always slews servo to min or max.
At least, being done with eventual hot key support in mind,
that effort should be less extensive..

Updates are posted to GitHub;
known bugs at this point are some message being unnecessarily repeated.
 
Last edited:
Upvote 0

Servo parameter management​

Managing more than a pair of hobby servos can get tricky.

The Arduino library supports values 1-179 to hobby servos,
roughly corresponding to 180 degree rotation for some servos
and 270 degrees for others.
Harness tensioning by servo arms want 180 degree rotation;
if less than 180 degrees are needed, then shorter arms are wanted.
Getting exactly the right 180 degrees involves
fiddling with arm splines for 180 degree servos, or
just changing an offset value for 270 degree servos.
180 degrees of rotation for a 270 degree servo corresponds to
an Arduino servo value difference of 120, fitting nicely into 7 bits.
Values are typically established for an offset setting
by finding smallest servo current consumptions with slack harnesses.
A maximum tension setting, tmax will be established relative to that offset,
with allowable tension values between 0 and that tmax,
to which an Arduino sketch will add offset for the servo API.

However​

  • if acceleration is to be simulated e.g. by air wedge G-seat
    then additional harness slack will be wanted during accelerations.
    • this wants an additional setting for minimum slack values
      lower the nominal offset.
    • If deceleration is considered positive, then acceleration is negative
      and will be limited to a range from 0 to minimum - offset.
    • Similar arithmetic applies for servos driving air wedges, where
      • values below some offset try to draw air from the seat wedge
      • values above offset force air into that wedge.
      • of course, changes from any value to a lower value also imply air removal.
  • There may be other configurations, e.g. using servo sprockets instead of arms,
    where ranges of more than 127 values are wanted. Those cases want a gain factor:
    • control values to be reduced by that factor before sending to Arduino
    • .. which Arduino multiplies by that factor for e.g. the servo API.
    • gain factors to be e.g. in increments of 1/64, so 64 == unity gain,
      allowing Arduino to multiple by that gain factor, add 32, then shift right by 6, avoiding division.
Unique min, max, offset and gain values may be wanted for up to 15 PWM devices.
 
Last edited:
Upvote 0

Progress​

Moderately intense remote paired programming with @sierses has yielded
a useful SimHub profile and STM32duino sketch for Black or Blue Pills
that can support up to 15 hobby servos, PC fans or other PWM devices
.
The user interface has 4 pages, of which the first sets telemetry controls:
Page1.gif

Three other pages have sliders for each configured PWM pin
to set minimum and maximum excursion
as well as PWM setting for telemetry signal -== 0, e.g.:
Page2.gif
 
Upvote 0
Unique min, max, offset and gain values
Code is (barely) stable enough to try a next step, namely calibration assistance.
Many sim apps, including SimHub, have "automatic" calibration options.
Generally, these options are undocumented and idiosyncratic.
This option is also highly idiosyncratic, but at least is here documented:
  • An Info slider option was added, namely 5=gnuplot.
    Info5.gif
    • It does NOT run gnuplot
    • Instead, it places the Arduino device in ASCII echo mode, so that
    • gnu-plottable data may be logged to Incoming serial data:
      incoming.gif
  • To generate a gnuplot-able log, replay a SimHub game record
    replay.gif
  • Drag the mouse pointer over logged data, copy and paste to e.g. gnuplot.txt
  • gnuplot> p 'gnuplot.txt' u 1 w l t 'surge', '' u 2 w l t 'sway', '' u 3 w l t 'heave'
    M3Spielberg.png

    telemetry properties from which ts[] values are calculated for PWM channels

  • gnuplot> p 'gnuplot.txt' u 6 w l t 'ts[2]', '' u 7 w l t 'ts[3]', '' u 8 w l t 'ts[4]'
    ts.png

    These channel values will be smoothed and sent to Arduino for Info settings < 4.
Values (after adding Offset) to Arduino > 126 or < 0 are clipped, e.g. ts[2] beyond sample 1100/1400;
a first step to reduce clipping: adjust Global gain:
global.gif
 
Last edited:
Upvote 0
The latest change on the experimental branch of blek2char, namely new2char,
now sources telemetry from effects grouped in a ShakeIt profile.
This has several benefits:
  • ShakeIt, at least in theory, can automatically normalize ("calibrate")
    telemetry property values for cars and games
  • ShakeIt effects include gamma and gain controls,
    removing the need for those controls in Custom serial profiles
  • ShakeIt Live effects graphs reduce the need for capturing data to gnuplot
    for tweaking telemetry gains
The downside is switching from the Custom serial devices plugin
to the ShakeIt Bass Shakers plugin to fool with those telemetry properties.
 
Last edited:
Upvote 0

Granularity​

Evolved from 7-bit control only for harness tensioning,
the sketch uses Arduino Servo API default, namely 180 ("degree") input values
despite using 270 degree servos, which were preferred for servo arm position tuning
without spline fiddling. Since 180 * 180/270 = 120, values conveniently fit 7 bits.

For more general use, the full servo pulse width range may be wanted;
alternatively, maximum granularity may be wanted for a constrained pulse width range.

The Arduino API supports specifying min and max pulse widths as well as min and max input values.
Under the covers, Arduino applies `map()` for every `Servo.write()`.
Consequently, the sketch will change to apply channel-specific min and max values
to pulse width step range at `Servo.attach()`, always with a 126 value for full input range.
 
Upvote 0
Before diving into sketch changes for improved granularity,
the custom serial profile was gone thru to sort some things,
specifically adjusting min before max before neutral servo positions.
Checking that change flushed out bugs:
  1. The initial connect message sent wrong servo limit values to Black/Blue Pills.
    Values from servo setting changes would be correct, but confusing.
  2. Equally confusing was (mis)behavior during servo position settings,
    where servos would not slew to current values before slider changes.
Meanwhile @sierses has worked out non-linear smoothing to more strongly
attenuate small differences, reducing servo noise for imperceptible tactile changes.

Still trying to understand SimHub ShakeIt automatic calibration behavior,
but it appears that those calibrations are not saved
between at least recorded sessions for the same car and track.

Consequently, it is best to not enable the custom serial plugin,
which gets telemetry via ShakeIt effect properties,
until after those effects have been running for a lap or two.
 
Last edited:
Upvote 0
improved granularity
More bugs were found while preparing for improved granularity;
improving granularity actually ends up simplifying code,
once Servo degrees are replaced by more direct control of pulse width microseconds.

Getting 180 degree RDS5160 instead of 270 degree, was a mistake;
need to fiddle with splines instead of just dialing a minimum setting in software.
 
Upvote 0
Added support for: Arduino Uno/Nano; Arduino Leonardo/Micro; Sparkfun ProMicro; Arduino Mega 2560.

It compiles for each of these boards in the Arduino IDE, but is definitely in need of testing on actual hardware.
 

Attachments

  • blek2char multi.ino.txt
    11.3 KB · Views: 114
Upvote 0

Latest News

How long have you been simracing

  • < 1 year

    Votes: 347 15.5%
  • < 2 years

    Votes: 241 10.8%
  • < 3 years

    Votes: 239 10.7%
  • < 4 years

    Votes: 177 7.9%
  • < 5 years

    Votes: 299 13.4%
  • < 10 years

    Votes: 257 11.5%
  • < 15 years

    Votes: 164 7.3%
  • < 20 years

    Votes: 125 5.6%
  • < 25 years

    Votes: 99 4.4%
  • Ok, I am a dinosaur

    Votes: 288 12.9%
Back
Top