Expo Einstellung RC - Mathematik

So ich habe es jetzt mit sinh und tanh ausbaldowert.

Einfach nur prozentual rauf /runter rechnen reicht wahrscheinlich nicht.
Weil man ja den Y-Wert auch noch wieder auf 1023 normieren muss
und ein simples 50% rechnet das runter und die Skalierung auf den Ursprungswert hoch.

Damit die Kurve mal mehr mal weniger stark vom linearen Verlauf abweicht muss man den X-Wert mit einem Faktor multiplizieren. Damit kommen dann als Y-Werte zunächst von 1023 abweichende Werte heraus. Deshalb muss man den Y-Wert dann wieder nach der sinh / tanh-Berechnung wieder auf 1023 normieren.
Das passiert in dem Demo-Code beim Serial.print()

Das Grundprinzip wird in diesem Demo-Code gezeigt. du musst dann eben noch die Faktoren auf deine Bedürfnisse anpassen. Also kleinere Schritte für X und die Y-Werte hochskalieren von -+3 auf -+1023

#include <math.h>

double myX;
double myY;
double myY2;
double myY3;
double startAt;

double Min_y;
double Min_y2;
double Min_y3;

double fy;
double fy2;
double fy3;

double scaleUp   = 3.0;
double scaleDown = 0.1;

double scaleBack;


void setup() {
  Serial.begin(115200);
  printSinH();
  Serial.println();
  Serial.println();
  printTanH();
}


void printTanH() {
  startAt = -3.0;
  myX = startAt;

  Serial.println("TanH");
  Serial.print("X");
  Serial.print(";");
  Serial.print("Y"); 
  Serial.print(";");
  Serial.print("Y2");
  Serial.print(";");
  Serial.println("Y3");

  Min_y  = tanh(startAt);
  Min_y2 = tanh(startAt * scaleUp);
  Min_y3 = tanh(startAt * scaleDown);

  scaleBack = startAt / Min_y2;

  fy  = Min_y2 / Min_y  * scaleBack;
  fy2 = Min_y2 / Min_y2 * scaleBack;
  fy3 = Min_y2 / Min_y3 * scaleBack;
    
  for (int i = int(startAt * 10); i <= (-1.0 * startAt * 10); i++) {
    myY  = tanh(myX);
    myY2 = tanh(myX * scaleUp);
    myY3 = tanh(myX * scaleDown);
    Serial.print(myX);
    Serial.print(";");
    Serial.print(myY * fy);
    Serial.print(";");
    Serial.print(myY2 * fy2);
    Serial.print(";");
    Serial.print(myY3 * fy3);
    
    Serial.println();
    myX += 0.1;
  }
}


void loop() {

}

void printSinH() {
  startAt = -3.0;
  myX = startAt;

  Serial.println("SinH");

  Serial.print("X");
  Serial.print(";");
  Serial.print("Y"); 
  Serial.print(";");
  Serial.print("Y2");
  Serial.print(";");
  Serial.println("Y3");

  Min_y  = sinh(startAt);
  Min_y2 = sinh(startAt * scaleUp);
  Min_y3 = sinh(startAt * scaleDown);

  scaleBack = startAt / Min_y2;

  fy  = Min_y2 / Min_y  * scaleBack;
  fy2 = Min_y2 / Min_y2 * scaleBack;
  fy3 = Min_y2 / Min_y3 * scaleBack;


  for (int i = int(startAt * 10); i <= (-1.0 * startAt * 10); i++) {
    myY = sinh(myX);
    myY2 = sinh(myX * scaleUp);
    myY3 = sinh(myX * scaleDown);
    
    Serial.print(myX);
    Serial.print(";");
    Serial.print(myY * fy);
    Serial.print(";");
    Serial.print(myY2 * fy2);
    Serial.print(";");
    Serial.print(myY3 * fy3);
    
    Serial.println();
    myX += 0.1;
  }
}

Ich habe dann die XY-Wertepaare mit libreOffice als XY-Diagramm dargestellt um den Kurvenverlauf zu prüfen

vgs

Hier eine Codeversion die voll durchparametrisiert ist
Intervall für sinh/tanh
Bereich auf den die sinh/tanh-Werte hochskaliert werden sollen
automatisch berechnete Schrittweite

// MACRO-START * MACRO-START * MACRO-START * MACRO-START * MACRO-START * MACRO-START *
// a detailed explanation how these macros work is given in this tutorial
// https://forum.arduino.cc/t/comfortable-serial-debug-output-short-to-write-fixed-text-name-and-content-of-any-variable-code-example/888298

#define dbg(myFixedText, variableName) \
  Serial.print( F(#myFixedText " "  #variableName"=") ); \
  Serial.println(variableName);

#define dbgi(myFixedText, variableName,timeInterval) \
  { \
    static unsigned long intervalStartTime; \
    if ( millis() - intervalStartTime >= timeInterval ){ \
      intervalStartTime = millis(); \
      Serial.print( F(#myFixedText " "  #variableName"=") ); \
      Serial.println(variableName); \
    } \
  }

#define dbgc(myFixedText, variableName) \
  { \
    static long lastState; \
    if ( lastState != variableName ){ \
      Serial.print( F(#myFixedText " "  #variableName" changed from ") ); \
      Serial.print(lastState); \
      Serial.print( F(" to ") ); \
      Serial.println(variableName); \
      lastState = variableName; \
    } \
  }

#define dbgcf(myFixedText, variableName) \
  { \
    static float lastState; \
    if ( lastState != variableName ){ \
      Serial.print( F(#myFixedText " "  #variableName" changed from ") ); \
      Serial.print(lastState); \
      Serial.print( F(" to ") ); \
      Serial.println(variableName); \
      lastState = variableName; \
    } \
  }
// MACRO-END * MACRO-END * MACRO-END * MACRO-END * MACRO-END * MACRO-END * MACRO-END *

#include <math.h>

double myX;
double range = 1023.0;
double dx;
double numberOfSteps;

double myY;
double myY2;
double myY3;

double Min_y;
double Min_y2;
double Min_y3;

double fy;
double fy2;
double fy3;

double scaleUp   = 3.0;
double scaleDown = 0.1;

double scaleBack;
double startAt = -3.14;
double scaleTo1023 = range / (abs(startAt) );


void setup() {
  Serial.begin(115200);
  dx = ( abs(startAt) ) / range;
  Serial.print("dx=");
  Serial.println(dx,8);
  numberOfSteps = 1 / dx;
  dbg("0",numberOfSteps);
  
  printSinH();
  Serial.println();
  Serial.println();
  printTanH();
}


void printTanH() {
  myX = startAt;

  Serial.println("TanH");
  Serial.print("X");
  Serial.print(";");
  Serial.print("Y"); 
  Serial.print(";");
  Serial.print("Y2");
  Serial.print(";");
  Serial.println("Y3");

  Min_y  = tanh(startAt);
  Min_y2 = tanh(startAt * scaleUp);
  Min_y3 = tanh(startAt * scaleDown);

  scaleBack = startAt / Min_y2;

  fy  = Min_y2 / Min_y  * scaleBack;
  fy2 = Min_y2 / Min_y2 * scaleBack;
  fy3 = Min_y2 / Min_y3 * scaleBack;
    
  for (long i = long(startAt * numberOfSteps ); i <= (-1.0 * startAt * numberOfSteps ); i++) {
    myY  = tanh(myX);
    myY2 = tanh(myX * scaleUp);
    myY3 = tanh(myX * scaleDown);
    
    Serial.print(myX * scaleTo1023);
    Serial.print(";");
    Serial.print(myY * fy * scaleTo1023);
    Serial.print(";");
    Serial.print(myY2 * fy2 * scaleTo1023);
    Serial.print(";");
    Serial.print(myY3 * fy3 * scaleTo1023);
    
    Serial.println();
    myX += dx;
  }
}


void loop() {

}

void printSinH() {
  myX = startAt;

  Serial.println("SinH");

  Serial.print("X");
  Serial.print(";");
  Serial.print("Y"); 
  Serial.print(";");
  Serial.print("Y2");
  Serial.print(";");
  Serial.println("Y3");

  Min_y  = sinh(startAt);
  Min_y2 = sinh(startAt * scaleUp);
  Min_y3 = sinh(startAt * scaleDown);

  scaleBack = startAt / Min_y2;

  fy  = Min_y2 / Min_y  * scaleBack;
  fy2 = Min_y2 / Min_y2 * scaleBack;
  fy3 = Min_y2 / Min_y3 * scaleBack;


  for (long i = long(startAt * numberOfSteps); i <= (-1.0 * startAt * numberOfSteps); i++) {
    myY = sinh(myX);
    myY2 = sinh(myX * scaleUp);
    myY3 = sinh(myX * scaleDown);
    
    Serial.print(myX * scaleTo1023);
    Serial.print(";");
    Serial.print(myY * fy * scaleTo1023);
    Serial.print(";");
    Serial.print(myY2 * fy2 * scaleTo1023);
    Serial.print(";");
    Serial.print(myY3 * fy3 * scaleTo1023);
    
    Serial.println();
    myX += dx;
  }
}

vgs

Ich hoffe, Du meinst nicht die Werte des AD-Wandlers, weil der 10 Bit hat, wobei die Randbbereiche nahe 0 und 4095 nicht linear und damit nicht nutzbar sind. Außerdem kann der AD-Wandler nur positive Werte.

Gefühlt seit einer Ewigkeit kämpfe ich mit LibreOffice, um die mittels ESP32 errechneten Kurven dargestellt zu bekommen, jetzt stiehlst Du mir in den letzten Minuten auch noch die Schau. Na gut, Dir gebührt der Pokal :trophy:

Als Du von Sinus Hyperbolicus schriebst, mußte ich mich erstmal schlau machen, seit der lange zurückliegenden Schule hatte ich nichts mehr damit gemacht. Da sinh() beim ESP32 "eingebaut" ist und double auch mehr signifikante Stellen bringt1), hatte ich schnell ein Ergebnis.

Dann LibreOffice ... da habe ich mich blöd ... egal.

@themanfrommoon: Der Trick besteht darin, auf der Kurve "herumzurutschen", also mal den lineareren Teil zu nutzen, bei mir e = 1, mal den kurvigeren, bei mir e = 5.

Programm getestet mit ESP32
void kurve(double e)
{
  double ymax = sinh(1.0 * e);
  for (double xd = -1.0; xd <= 1.0; xd += 0.1)
  {
    double yd = sinh(xd * e);
    int x = int(xd * 1023);
    int y = int(yd / ymax * 1023);
    Serial.printf( "%d;%d\n", x, y );
  }
  Serial.println();
}

void setup() {
  Serial.begin(115200);
  delay(500);
  kurve(1.0);
  kurve(5.0);
}

void loop() {}
Grafik


Anm.:

  1. Auf AVRs könnte es zu etwas anderen Werten kommen.

Hallo @agmue,

sehr schön elegant kompakter Code.
vgs

Ne. Wenn man Exponential-Verhalten will dann soll auch im mittleren Bereich die Exponentialkurve benutzt werden. Das ist ja gerade der Gag. Um den Nullpunkt herum wenig Änderung (sinh)
oder umgekehrt
extra viel Änderung (tanh)

sonst hat man doch beim Übergang von linearer Funktion auf Expo - an welcher Stelle auch immer - plötzlich einen Sprung drin.

vgs

Doch :slightly_smiling_face:

Ich hatte Deine Kurve aus #11 und das Foto aus #12 vor Augen. Zum Tangens Hyperbolicus habe ich es noch nicht geschafft.

Der Sinus Hyperbolicus ist um den Ursprung -1 bis +1 fast linear, während es außerhalb -5 bis +5 schön krumm wird. Wenn man das normiert verwendet, hat man die gewünschte Veränderung der Krümmung.

Im Thema finde ich keinen Bedarf für "extra viel Änderung (tanh)", kann mich aber irren. Beim Übergang von sinh nach tanh könnte es auf meine Art etwas holprig werden. Bei einer Fernsteuerung hätte ich wenig Bedenken, aber bei der für uns geheimen Anwendung muß sich @themanfrommoon äußern, ob und wie er das benötigt.

Ja dann müsste @themanfrommoon mal genauer beschreiben wann sich die "Exponentialität" ändert-

Ich verstehe das so:
Es wird eine bestimmte Exponentialität eingestellt. Beispiel +25% und dann läuft das Programm auf der +25% Kurve.

Für eine andere Betriebssituation wird die +60%-Kurve eingestellt.

Oder es wird beobachtet wie verändert sich das Verhalten dessen was da gesteuert wird wenn man an einem Poti dreht und abhängig von der Potistellung wird dann
1%, 2%, 5%........100% Expo ausgegeben.

Und deshalb wird die entsprechende Formel wie man jetzt vom X-Wert am "Eingang" auf den Y-Wert kommt der zu soundso viel % Expo gehört benötigt.

Das macht die Funktion ja. Wenn das Ausgeben der Y-Werte je ultraschnell gehen soll dann müsste man eben die Y-Werte in einen Array mit 1023 Elementen ablegen dann kann man direkt expoY[X] ausgeben.
Wenn mit dem Verstellen des Expo-Wertes auch ultraschnell die neue Kurve da sein soll dann ist vielleicht an Teensy 4.1 angesagt. Mit 600 MHz und floating-point unit 64 bit dürfe es schnell genug gehen.

@themanfrommoon
Es wäre jetzt schon seeehr interessant in was für einem Projekt du das einsetzen willst.

vgs

so wie im Video
0 ist Linear.
"Parameter im plus" haben einen flachen Anstieg (tanh) der Steiler wird (auf der positiv Seite)
"Parameter im minus" haben den steilen Anstieg der flacher wird (sinh) (auf der Positiv Seite)

als Interface wäre das wohl eine c++ Funktion mit zwei Eingabeparameter und einem Rückgabewert.

1 Like

sinh / tanh sind auch hübsch aber "expo" ist so:

void setup() {
  Serial.begin(115200);

  const float expo = 0.5; // 50 %  (0 istlinear)

  for (float x = 0.0;x <=1.001; x+=0.1) {
    Serial.print(x,1); Serial.print (": "); 
    Serial.println(pow(x,1.0+expo),4);
  }
}

Aus 0...1023 0...1.0 und umgekehrt zu machen, sollte zu schaffen sein :slight_smile:

Wenn die Werte von einem Poti per analogRead kommen (und da ein Mensch dran dreht), ist im Vergleich alles ultraschnell. selbst sinh.

1 Like

Oh mann, Wahnsinn! Was für eine unglaubliche Input Flut. Wow, vielen Dank!
Ich muss erstmal mit dem Lesen und Beantworten hinterherkommen.

Also, wo soll das eingesetzt werden:
Es soll ein Rasenmähroboter mit einer RC Fernbedienung gesteuert werden. (das ist also keine ultraschnelle Anwendung).
Die RC Fernbedienung soll die kostengünstigste sein, die es gibt: 2 Steuerknüppel und ein Schalter. Aus dem Grund hat der Sender leider keine eingebaute Expo Funktion.
Das Signal habe ich schon sauber aus dem Empfänger raus. Da sind nicht einmal die Wege in beide Richtungen zahlenmäßig identisch groß, aber ich habe sie mit map(); auf +/-1023 normiert.
Da der Rasenmähroboter keinen Antriebsmotor und separate Lenkung hat, sondern nur zwei Antriebsmotoren, die durch differentielle Steuerung zum Antreiben und Steuern genutzt werden, habe ich bereits einen funktionierenden Kreuzmischer gebaut. Ich habe also für Antrieb und Lenkung jeweils den Wertebereich +/-1023.
Funktionieren tut das, aber das Fahrgefühl ist sehr nervig. Die Lenkung ist zu stark und man neigt zum Übersteuern. Jetzt könnte man einfach nur die Wege begrenzen, richtig, aber dann ist gleichermaßen auch die Wendegeschwindigkeit eingeschränkt, und man gewinnt auch nur linear an Steuerpräzision.
Im RC-Modellbau hat man die gleichen Probleme, und dort löst man sie mit der Expo Funktion.
Oft sind 30% Expo ein guter Wert für eine präzise Steuerung.

Der Expo Wert muss nicht unbedingt während der Fahrt verändert werden.
Es würde ausreichen ein Array anzulegen, dass dann im Setup durch eine Formel gefüllt wird.
Ich möchte aber kein Array aufsetzen, in dem jeder Wert aus Excel oder sonstwo hergeholt werden muss.
Ich baue das nicht nur für mich, sondern für eine Community. Da wird jeder einen anderen Sender haben und jedem gefällt ein anderes Steuerverhalten besser.
So kann dann später im Sketch das Steuerverhalten durch Änderung einer Variable eingestellt werden.
d.h. ist die Steuerung jemandem zu stark -> erhöhe den Expo Wert um 5 oder 10 und probiere es nochmal aus.
Ist die Steuerung zu lahm -> verringere den Expowert um 5 oder 10 und probiere es nochmal aus.
Auch bei vorwärts/rückwärts könnte die Expo Funktion interessant sein, wenn es darum geht eine Position genau anzufahren.

Warum wird der Rasenmähroboter überhaupt per Fernsteuerung gesteuert?

  1. Just for fun und weil man es kann
  2. Es ist ein RTK-GPS Mähroboter, der automatisch navigiert und fährt, aber auch der muss ja angelernt werden. Die Positioniergenauigkeit liegt bei etwa 2cm. Man kann den schweren Mähroboter auch von Hand rumtragen und positionieren, oder eben mit einer Handyapp über bluetooth oder WLAN steuern. Das bereitet aber so viele Probleme mit der Verbindung und der Latenz und auch mit der Touchbedienung, dass man von präziser Steuerung nicht reden kann. Die Steuerung, die ich hier realisieren möchte ist sehr viel präziser.

Zunächst geht es erstmal nur um das "verbiegen" des Verhaltens im Zahlenbereich von +/-1023.
Wenn das geschafft ist, gehen die Zahlen durch den Kreuzmischer.
Wenn das erledigt ist, werden die Zahlen per I²C an das Mähroboter IO-Board weitergegeben.

so, jetzt muss ich erstmal die ganzen Posts lesen....

Beste Grüße,
Chris

1 Like

Danke für die ausführliche Beschreibung, Dein Projekt liest sich spannend :slightly_smiling_face:

Ich kann euch auch meinen Sketch bis dahin zeigen, nur ist da noch rein garnichts in Richtung Expo und I²C drin. Es werden bisher nur die Werte aus dem Empfänger geholt und mit dem Kreuzmischer gemischt und seriell ausgegeben. Das nützt hier also noch gar nix:

[code]
#include "sbus.h"             // from https://github.com/bolderflight/sbus
bfs::SbusRx sbus_rx(&Serial1, 16, 17, true);  // declaration of rx and tx pins between FrSky receiver and ESP32 dev board, we only need the rx
bfs::SbusData data;

short throttle_min = 172;     // determined value on my setup for throttle minimum position
short throttle_mid = 925;     // determined value on my setup for throttle middle position
short throttle_max = 1811;    // determined value on my setup for throttle maximum position
short steering_left = 172;    // determined value on my setup for steering left position
short steering_mid = 990;     // determined value on my setup for steering middle position
short steering_right = 1811;  // determined value on my setup for steering right position
short mow_full = 1811;        // determined value on my setup for mow motor full power position
short mow_off = 172;          // determined value on my setup for mow motor off position
byte throttle_deadband = 20;  // deadband for throttle middle-position to avoid slow movement around middle-position, and both end positions
byte steering_deadband = 20;  // deadband for steering middle-position to avoid slow movement around middle-position, and both end positions
short throttle_stick;         // input throttle stick
short steering_stick;         // input steering stick
short mow_pot;                // input potentiometer mow motor
short throttle;               // throttle value
short steering;               // steering value
short motor_mow;              // output for right motor
short motor_left;             // output for left motor
short motor_right;            // output for right motor


void setup() {
  Serial.begin(115200);
  while (!Serial) {}
  sbus_rx.Begin();
}

void loop () {
  if (sbus_rx.Read()) {
    data = sbus_rx.data();
    // --------------------throttle stick--------------------
    throttle_stick = data.ch[0];
    if (throttle_stick > throttle_mid + throttle_deadband / 2) {
      throttle = (map(throttle_stick, throttle_mid + throttle_deadband / 2, throttle_max, 0, 1023));
    } else {
      if (throttle_stick < throttle_mid - throttle_deadband / 2) {
        throttle = (map(throttle_stick, throttle_mid - throttle_deadband / 2, throttle_min, 0, -1024));
      } else {
        throttle = 0;
      }
    };

    // --------------------steering stick--------------------
    steering_stick = data.ch[1];
    if (steering_stick > steering_mid + steering_deadband / 2) {
      steering = (map(steering_stick, steering_mid + steering_deadband / 2, steering_right, 0, 1023));
    } else {
      if (steering_stick < steering_mid - steering_deadband / 2) {
        steering = (map(steering_stick, steering_mid - steering_deadband / 2, steering_left, 0, -1024));
      } else {
        steering = 0;
      }
    };

    // --------------------mowmotor potentiometer--------------------
    mow_pot = data.ch[4];
    motor_mow = (map(mow_pot, mow_off, mow_full, 0, 1023));

    // --------------------failsafe--------------------
    if (data.failsafe > 0) {
      Serial.print("failsafe");    // if a failsafe is recognized by the FrSky receiver
      throttle = 0;
      steering = 0;
      motor_left = 0;
      motor_right = 0;
      motor_mow = 0;
    }
    else {
      Serial.print("running");    // if no failsafe is recognized by the FrSky receiver
    }

    // --------------------cross-mixer left motor--------------------
    if (throttle + steering > 0) {                 // if the sum of throttle and steering is forward
      if (throttle + steering < 1023) {            // if the sum of throttle and steering is less the full forward
        motor_left = throttle + steering;          // then motor_left = throttle + steering
      } else {
        motor_left = 1023;                         // else motor_left =  full forward
      }
    } else {                                       // else the sum of throttle and steering is backward
      if (throttle + steering > -1023) {           // if the sum throttle and steering is less then full backwards
        motor_left = throttle + steering;          // then motor_left = throttle + steering
      } else {
        motor_left = -1023;                        // else motor_left = full backwards
      }
    }
    
    // --------------------cross-mixer right motor--------------------
    if (throttle - steering > 0) {                 // if the difference between throttle and steering is forward
      if (throttle - steering < 1023) {            // if the difference between throttle and steering is less the full forward
        motor_right = throttle - steering;         // then motor_right = throttle - steering
      } else {
        motor_right = 1023;                        // else motor_right =  full forward
      }
    } else {                                       // else the difference between throttle and steering is backward
      if (throttle - steering > -1023) {           // if the difference between throttle and steering is less then full backwards
        motor_right = throttle - steering;         // then motor_right = throttle - steering
      } else {
        motor_right = -1023;                       // else motor_right = full backwards
      }
    }

    // --------------------send values on I²C--------------------
    Serial.print("\t");
    Serial.print("\t");
    Serial.print(throttle);
    Serial.print("\t");
    Serial.print(steering);
    Serial.print("\t");
    Serial.print(motor_left);
    Serial.print("\t");
    Serial.print(motor_right);
    Serial.print("\t");
    Serial.println(motor_mow);
  }
}
[/code]

Doch das bringt was. Nämlich das man sieht wie die Variablen heißen und dass man weiß es gibt einen Kreuzmischer. Vielleicht hat das Einfluss darauf wie man bei einem Kreuzmischer Expo einbaut. Also ob Expo machen vor oder hinter der Kreuzmischung besser ist.
vgs

@agmue : Cool! Aber e=1 ist nicht linear! Halte mal ein DIN A4 Blatt quer über den Bildschirm und den Rand des Blattes über die blaube Kurve. -> das ist auch eine leichte S-Kurve.
e=0.0 geht nicht
e=0.1 ist schon dicht dran
e=0.01 ist noch dichter dran, gerundet sieht das schon linear aus. Wenn man aber mal die Zahlen zwei Zehnerpotenzen größer macht sieht man hier auch noch Abweichungen von der linearen Kurve, es ist also immer noch eine S-Kurve

Ich auch (noch) nicht. Ich gehe davon aus, dass die Abschwächung um den Nullpunkt ausreichen dürfte. Die andere Richtung wäre nur der Vollständigkeitshalber da. Ich ging aber zuerst auch davon aus, dass das es einfach eine diagonalgespiegelter Verlauf ist, in meinem Video sieht man aber, dass es nicht so ist, und auch nicht so sein darf, weil ein Servo in Mittelstellung extrem nervös bis undefiniert ist.

Das Expo kommt natürlich vor dem Kreuzmischer rein.
Expo soll für die reine Lenkung (und vielleicht auch für vorwärts/rückwärts) sein.
Nach dem Kreuzmischer sehe ich keinen Sinn darin.
Also Expo soll auf "steering" angewendet werden (und vielleicht auch mit anderem "Exponenten" auf "throttle").

Habe ich das irgendwo behauptet? Ich schrieb

mal den lineareren Teil

das ist nicht "linear".

Also e = 1 ist linearer als e = 5, bleibt aber eine S-Kurve.

Aber brauchst Du einen mathematischen Beweis für die Nichtlinearität vom Sinus Hyperbolicus oder geht es um das Verbiegen eines Steuerknüppelsignals?

Das habe ich mal aufgegriffen, weil es so schön kompakt ist.
Und dann habe ich mal die 100% Expo Kurve aus meinem Sender "ausgemessen" und in ein Diagramm gepackt. Und dann habe ich Michaels Formel genommen und mal rumgespielt.
Die 100% Kurve aus meinem Sender passt sehr gut zu Michaels Formel mit dem Exponenten 2:
(grün und dunkelblau sind so deckungsgleich, dass man es fast nicht sieht, dass da zwei Kurven übereinander liegen)

Dagegen habe ich nochmal @agmue Variante aus #24 gehalten, das sieht so aus (die Knicke in der Kurve sind wohl durch die integer Berechnung begründet, aber für den Vergleich reicht das aus):

Probiert habe ich Kurve 1.0 (hier nicht dargestellt) Kurve 5.0, Kurve 4.0 und Kurve 3.0
Alle sind um den Nullpunkt zu steil.

Demnach denke ich müsste @StefanL38 Variante die richtige sein.

Beste Grüße und besten Dank,
Chris

Probiere mal e kleiner 1
Ich habe für die Kurve die um 0 herum ganz flach ist Faktor 0,1. Also e = 0,1
vgs