Saxxon's Simukit 3DOF 4 Motoren Build Thread

U

User 15867

Erst einmal vielen Dank für euren Zuspruch zu meinem Build Log.
Macht viel mehr Spaß, wenn man sieht, dass es anderen gefällt.


Wie ihr vielleicht gelesen habt, habe ich jüngst die Möglichkeit entdeckt die Stromspannung an den Netzteilen zu verstellen.
Da einige der Motoren unterschiedlich leistungsstark waren und teilweise mit meinem Gewicht zu kämpfen hatten (bin nicht der leichteste), habe ich mir gedacht mal schauen was passiert, wenn ich die Motoren über der Spezifikation betreibe.

Gesagt getan und bei Netzteile auf 26 Volt hochgedreht.

Anhang anzeigen 55201

Und der Effekt war deutlich spürbar.
Plötzlich haben diese kleinen Motoren überhaupt kein Problem mehr mit mir und selbst bei kleinsten Bewegungen reagieren sie jetzt super. Als Dank durfte ich natürlich das komplette Setup überarbeiten, aber es hat sich voll gelohnt.

Normalerweise sind Motoren für spezielle Spannungen ausgelegt und optimiert. Hier eigentlich 24 Volt.
Aber vielleicht ist das in China ein wenig anders.
Wollte euch den Tipp nicht vorenthalten.
Evtl. kann ein E-Techniker was dazu sagen. Mal sehen, ob die Kohlen das lange mitmachen.



Hallo Saxxon66

Interessanter Ansatz,

hast Du die Volt und Amper schon mal am Motoreingang mit einem Messgerät gemessen.

Meine Motoren wurden durch einstellen von PID und ins besonders durch erhöhen von P deutlich agiler.


Nein, durchgemessen habe ich sie noch nicht.
Ich verlasse mich da auf die Sabertooths. DEScribe kann die Daten loggen und als CSV ausgeben. Per Excel kann man sehr gut auswerten.

Aber klar ist auch, dass sich die Wattleistung durch die Erhöhung der Spannung sich steigert (A*V).
Die Motoren sollten eigentlich eine optimale Effizienz bei einer bestimmten Spannung haben (24V nominal).
Z. B. würde es ineffizient sein die Spannung auf 10 V zu stellen und die Ampere auf 24 A.
Das ergibt zwar auch 240 Watt aber die Spannung reicht dann nicht aus.
Die Spannungserhöhung hat aber bei mir einen deutlichen Effekt gebracht.
Bei mir bin ich von 24V auf 26V bei max 9 Ampere = 234 Watt. Das ist deutlich über der Angabe von 120 Watt.
Temperaturen passen aber, immer noch handwarm.

Probier es doch einfach mal aus, vielleicht verschafft es deinen auch Flügel ;)

Zum P bei PID.
P hat normalerweise den stärksten Einfluss, da hier direkt der Fehler vom Setpoint zur aktuellen Position berechnet wird.
Es kommt jedoch auf die PID Implementierung an.
Bei unseren Potis haben wir einen Wertebereich von 0 - 1023, die Werte für die Stromstärke im Sabertooth gehen von -127 - +127. Bei einer Änderung von von 127 Units im Poti wird also schon die maximale Stromstärke vom Sabertooth an die Motoren gegeben und das bei P = 1. Bei mir habe ich P auf Faktor 1,7 eingestellt. Somit erreiche ich schon die Maximale Leistung bei 58 Units.
Hoffe das macht Sinn was ich hier schreibe, keine einfach zu beschreibende Materie (für mich).
 
U

User 12473

Hallo Saxon

Deiner Erklärungen machen für mich durch aus Sinn

P interpretiere ich auch als Beschleunigungsparameter


Wo im Netzteil hast Du Volt erhöht ?
 
U

User 15867

Arduino Sketch + Sourcen

Für alle die an der Programmierung interessiert sind, hier meine Arduino Sourcen.

Arduino Sketch

Code:
//
// MotionFeedback
//
// Version 0.3 alpha
// Autor: mra
//


#include <SoftwareSerial.h>
#include <Sabertooth.h>
#include <CmdMessenger.h>
#include <MotionFeedback.h>
#include <TimerOne.h>


// defines for setting and clearing register bits
#ifndef cbi
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#endif
#ifndef sbi
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))
#endif


#define INT_MAX 2147483647


// **************************************************************************
// General Setup
// **************************************************************************


#define VERSION "00.01.03"


// poti pins
const int potPinFR = 0;
const int potPinRR = 1;
const int potPinFL = 2;
const int potPinRL = 3;


// Sabertooth pins
int sabertoothPin1 = 12;
int sabertoothPin2 = 11;


// number of motors in the sim
const int MOTOR_COUNT = 4;


// repeat time of the main loop
const unsigned int LOOP_IN_MS = 5;


const unsigned int SAMPLE_RATE = 10000; in nano seconds


// ADC speeed up hack
#define FASTADC  1 //Hack to speed up the arduino analogue read function, comment out with to disable this hack




// **************************************************************************
// Internal variables * DO NOT CHANGE *
// **************************************************************************


// timestamp of the last loop run
unsigned long lastLoopTimeMS = 0;


// position range from [-512 - 512]
// power range from [0 - 10]
int position[4] = {0, 0, 0, 0};
int power[4] = {0, 0, 0, 0};




// **************************************************************************
// Sabertooth Setup
// **************************************************************************
SoftwareSerial SWSerial1(NOT_A_PIN, sabertoothPin1);
SoftwareSerial SWSerial2(NOT_A_PIN, sabertoothPin2);
Sabertooth  ST1(128, SWSerial1);
Sabertooth  ST2(128, SWSerial2);


// **************************************************************************
// Motor Setup
// **************************************************************************


// Global Deadzone for the startup
int DEAD_ZONE = 15;


// Motor params:
//    id        - The id for the motor
//    active    - Motor is active and can drive the sim
//    controller - The Sabertooth controller instance
//    controllerNumber - Motor number of the Sabertooth (M1=1, M2=2)
//    potiPin   - Arduino Pin for the motor sensor
//    rangeMin  - Minimum position of the motor sensor (0 - 1024)
//    rangeMax  - Maximum position of the motor sensor (0 - 1024)
//    deadZone  - If the next position is in the range current position +- deadzone the motor will not drive
//


// The position for the motors in the list is importend!!!
// From the driver seat position 
//  1. Front right
//  2. Rear Right
//  3. Rear left
//  4. Front left




// Motor(byte id, bool active, USBSabertooth& controller, byte controllerNumber, byte controllerPin,
//        uint16_t potiPin, int rangeMin, int rangeMax, int deadZone, int centerOffset


Motor motors[MOTOR_COUNT] = {
    Motor(1, true, ST1, 1, 2, potPinFR, 370, 640, DEAD_ZONE, 0) // FR
  , Motor(2, true, ST1, 1, 1, potPinRR, 370, 640, DEAD_ZONE, 0) // RR
  , Motor(3, true, ST2, 2, 2, potPinRL, 370, 640, DEAD_ZONE, 0) // RL
  , Motor(4, true, ST2, 2, 1, potPinFL, 370, 640, DEAD_ZONE, 0) // FL  
};


bool driveMotor = true;


// **************************************************************************
// Serial Line Manager API Setup
// **************************************************************************
// Commands
enum
{
  kAcknowledge = 0,
  kError = 1,
  kMessage = 2,
  kSetPosition = 3 ,
  kGetPositionInfo = 4,
  kSetMotorParams = 5,
  kSetStandby = 6,
  kSetStop = 7,
  kStartAutotune = 8,
};


CmdMessenger cmdMessenger = CmdMessenger(Serial);


// Callbacks define on which received commands we take action
void attachCommandCallbacks()
{
  cmdMessenger.attach(OnUnknownCommand);
  cmdMessenger.attach(kSetPosition, OnSetPosition);
  cmdMessenger.attach(kGetPositionInfo, onGetPositionInfo);
  cmdMessenger.attach(kSetMotorParams, onSetMotorParams);
  cmdMessenger.attach(kSetStandby, OnStandby);
  cmdMessenger.attach(kSetStop, OnStop);
  cmdMessenger.attach(kStartAutotune, OnStartAutotune);
}


// ***********************************************************
// *                    Callbacks                            *
// ***********************************************************
void OnStartAutotune() {
  driveMotor = false;
  autoTune(motors, 4, 1.5);
  driveMotor = true;
}


// Called when a received command has no attached function
void OnUnknownCommand()
{
  cmdMessenger.sendCmd(kError, "Command without attached callback");
  // Serial.println("Unknow command");
}


void onSetMotorParams() {
  enum MOTOR_ID motorId = (MOTOR_ID)cmdMessenger.readInt16Arg();
  switch(motorId) {
    case ALL:
      for (int i = 0; i < MOTOR_COUNT; i++) {
        motors[i].setMin(cmdMessenger.readInt16Arg());
        motors[i].setMax(cmdMessenger.readInt16Arg());
        motors[i].setDeadZone(cmdMessenger.readInt16Arg());
        motors[i].setCenterOffset(cmdMessenger.readInt16Arg());
        motors[i].setKP(cmdMessenger.readFloatArg());
        motors[i].setKI(cmdMessenger.readFloatArg());
        motors[i].setKD(cmdMessenger.readFloatArg());
        motors[i].enablePID(cmdMessenger.readBoolArg());
      }
      break;
    default:
      motors[motorId].setMin(cmdMessenger.readInt16Arg());
      motors[motorId].setMax(cmdMessenger.readInt16Arg());
      motors[motorId].setDeadZone(cmdMessenger.readInt16Arg());
      motors[motorId].setCenterOffset(cmdMessenger.readInt16Arg());
      motors[motorId].setKP(cmdMessenger.readFloatArg());
      motors[motorId].setKI(cmdMessenger.readFloatArg());
      motors[motorId].setKD(cmdMessenger.readFloatArg());
      motors[motorId].enablePID(cmdMessenger.readBoolArg());
      break;
  }
}


void onGetPositionInfo() {
  cmdMessenger.sendCmdStart(kGetPositionInfo);
  for (int i = 0; i < MOTOR_COUNT; i++) {
    int *pinfo = motors[i].getPositionInfo();
    cmdMessenger.sendCmdArg(pinfo[0]);
    cmdMessenger.sendCmdArg(pinfo[1]);
    cmdMessenger.sendCmdArg(pinfo[2]);
    cmdMessenger.sendCmdArg(pinfo[3]);
  }
  cmdMessenger.sendCmdEnd();
}




void OnSetPosition()
{
  power[0] = cmdMessenger.readInt16Arg();
  position[0] = cmdMessenger.readInt16Arg();
  power[1] = cmdMessenger.readInt16Arg();
  position[1] = cmdMessenger.readInt16Arg();
  power[2] = cmdMessenger.readInt16Arg();
  position[2] = cmdMessenger.readInt16Arg();
  power[3] = cmdMessenger.readInt16Arg();
  position[3] = cmdMessenger.readInt16Arg();
}






// stop all motors
void OnStop()
{
  for (int i = 0; i < MOTOR_COUNT; i++) {
    motors[i].stop();
    position[i] = 0;
    power[i] = 0;
  }
}


void OnStandby()
{
  for (int i = 0; i < MOTOR_COUNT; i++) {
    motors[i].standby();
    position[i] = 0;
    power[i] = 0;
  }
}




// **************************************************************************
// Arduino methods
// **************************************************************************


void setup()
{
// Enable for faster analog read
#if FASTADC
  // set analogue prescale to 16
  sbi(ADCSRA, ADPS2) ;
  cbi(ADCSRA, ADPS1) ;
  cbi(ADCSRA, ADPS0) ;
#endif


  // setup serial connections
  Serial.begin(115200);
  SWSerial1.begin(115200);
  SWSerial2.begin(115200);


  // stop all motors
  OnStop();


  // drive to standby position
  OnStandby();


  // Attach my application's user-defined callback methods
  attachCommandCallbacks();


  // Setup the Interrupt Timer für Motor sampling
  Timer1.initialize(SAMPLE_RATE);
  Timer1.attachInterrupt(updateMotors); 
}


// Wait für the next telemetry inputs
void timing() {
  unsigned int loopTime = millis() - lastLoopTimeMS;
  //cmdMessenger.sendCmd(kMessage, loopTime);
  if (loopTime < LOOP_IN_MS) {
    delay(LOOP_IN_MS - loopTime);       // wait for a constant loop timing
  }
  //cmdMessenger.sendCmd(kMessage, millis() - lastLoopTimeMS);
  //Serial.println("Loop End");
  // we do need a constant loop time for PID calculations, so set the start
  lastLoopTimeMS = millis();
}


// Sample method, will be called by interrupt timers
void updateMotors() {
    for (int i = 0; i < MOTOR_COUNT; i++) {
      motors[i].update();
    }  
}


// Set the next Setppoint for all motors
void drive() {
  // position range from [-512 - 512]
  // power range from [0 - 10]
  if(driveMotor) {
    for (int i = 0; i < MOTOR_COUNT; i++) {
      motors[i].drive(position[i], power[i]);
    }  
  }  
}


int loopCounter = 0;


void loop()
{
    
  if(loopCounter == INT_MAX) loopCounter = 0;
  loopCounter++;
  if(loopCounter % 50 == 0) {
    onGetPositionInfo();
//  Uncomment for Debugging    
//    for (int i = 0; i < MOTOR_COUNT; i++) {
//      //cmdMessenger.sendCmd(kMessage,motors[i].toString());
//      Serial.println(motors[i].toString());
//    }
  }


  cmdMessenger.feedinSerialData();


  drive();


  timing();
}


Motor Code

Code:
#ifndef MotionFeedback_h
#define MotionFeedback_h   


#if defined(ARDUINO) && ARDUINO < 100
#error "This library requires Arduino 1.0 or newer."
#endif


#include <Arduino.h>
#include <stdio.h>
#include <Sabertooth.h>


#define PID  1


#define MAX_OUT  127
#define MIN_OUT -127




enum MOTOR_ID
{
    FR = 0,
    RR = 1,
    RL = 2,
    FL = 3,
    ALL = 256
};






// Motor is one of the Motion Sim motors
class Motor
{
public:
    // Motor constructor
    // Params: 
    //        active        - Motor is active and can drive the sim
    //        controller    - The Sabertooth controller
    //        controllerNumber - Motor number of the Sabertooth (M1=1, M2=2)
    //        potiPin        - Arduino Pin for the motor sensor
    //        rangeMin    - Minimum position of the motor sensor (0 - 1024)
    //        rangeMax    - Maximum position of the motor sensor (0 - 1024)
    //        deadZone    - If the next position is in the range current position +- deadzone the motor will not drive


    Motor(byte id, bool active, Sabertooth& controller, byte controllerNumber, byte controllerPin, uint16_t potiPin, int rangeMin, int rangeMax, int deadZone, int _centerOffSet);
    
    
    // position range [-512 - 512], speed range [0-10]
    void drive(int position, float power);


    // Update the Motor, bny checking positzion, calculation the power and communicate with arduino
    void Motor::update();


    // stop the motor
    void stop();


    // drive the motor to a neutral position (position = 0, poti = 512)
    void standby();


    // Set the minimum position for this motor.
    // This will not allow to drive the motor beyond this limit.
    void setMin(int value);


    // Set the maximum position for this motor.
    // This will not allow to drive the motor beyond this limit.
    void setMax(int value);


    // Set the deadzone for this motor.
    // This will give a range until the motor responds to requested position changes.
    // Primary used to minimize oszillation.
    void setDeadZone(int value);


    // Set the offset for the center position of the motor.
    // If the motor is not installed perfectly aligned to the other motors, 
    // this will help to level them to the others.
    void setCenterOffset(int value);


    // PID tuning Params
    void enablePID(bool enable);
    void setKP(float value);
    void setKI(float value);
    void setKD(float value);
    float getKP();
    float getKI();
    float getKD();


    // read the current position from the motor sensor
    int getCurrentPosition();


    // returns the go to and the current position
    int* getPositionInfo();


    // Returns some live info about this motor in a human readable form.
    const char* toString();


    int autoTuneTime;


private:
    byte _id;
    Sabertooth& _controller;    // Sabertooth controller associated with this motor
    byte _controllerNumber;        // Sabertooth controller number
    byte _controllerPin;        // Sabertooth controller pin (P1 or P2)
    bool _active;                // state of this motor
    uint16_t _potiPin;            // Arduino Pin to wich the potentiopeter is connected. Gives info of the m,otor position.
    int _rangeMin;                // Min position for this motor
    int _rangeMax;                // Max position for this motor
    int _deadZone;                // Deadzone for this motor
    int _centerOffSet; // Defines the offset to the center position


    int _lastPosition;
    int _currentPosition;
    int _goToPosition;
    int _power;


    // check if the position is save.
    // the save range is defined by rangeMin and rangeMax.
    bool checkRange(int position);


    // Sensor error correction properties
    float EMA_a = 0.6;      //initialization of EMA alpha
    float EMA_S = 0;


    // PID calculation
    int doPID(float setpoint, float currentPostion);






    // PID vars
#define SAMPLE_TIME_IN_SECOUNDS 0.005
    int sampleCount = 0;
    bool usePID = true;
    float preverror = 0;    // motor previous error
    float Ierror = 0; // motor intergral error
    float error = 0;        // error poti
    float derror = 0;        // derivative error 
    int lastSetpoint;
    
    // PID properties
    float KP = 1.0f; //PID proportional gain constant 
    float KD = 0.0f; //PID derivative gain constant
    float KI = 0.0f; //PID intergral gain constant
};




void autoTune(Motor* motors, int motorCount, float startP);


#endif



Code:
#include "MotionFeedback.h"



void autoTune(Motor* motors, int motorCount, float startP) {


    // init all motors
    for (int i = 0; i<motorCount; i++) {
        motors[i].setKP(startP);
        motors[i].setKI(0);
        motors[i].setKD(0);
    }


    int maxLoopCount = 20;
    float PmaxLimit = 5;
    float Pstep = 0.1;
    int distance = 100;
    int fromPosition = 0;
    int toPosition = fromPosition + distance;
    int loopCount = 0;


    RACE:
    // drive to start position
    for (int i = 0; i < motorCount; i++) {
        motors[i].drive(fromPosition, 0);
    }


    delay(2000);


    long startT = millis();
    for (int i = 0; i < motorCount; i++) {
        motors[i].drive(toPosition, 0);
        motors[i].autoTuneTime = 0;
    }
    
    // start the race


    int c = 0;
    while (true) {
        int t = millis() - startT;
        for (int i = 0; i < motorCount; i++) {
            int pos = motors[i].getCurrentPosition();
            int gotoPos =  motors[i].getPositionInfo()[0];
            if (pos >= gotoPos) {
                motors[i].autoTuneTime = t;
            }
            //else {
            //    float next = motors[i].getKP() + Pstep;
            //    if (next < PmaxLimit)
            //        motors[i].setKP(next);
            //}
        }


        bool allSet = true;
        for (int i = 0; i < motorCount; i++) {
            if (motors[i].autoTuneTime == 0) {
                allSet = false; 
                break;
            }
        }


        if (allSet) break;
        if (c > 200) break;
        c++;


        delay(5);


        for (int i = 0; i < motorCount; i++) {
            motors[i].drive(toPosition, 0);
        }
    }
     loopCount++;


     int winnerTime = motors[0].autoTuneTime;
     float maxP = motors[0].getKP();
     for (int i = 0; i < motorCount; i++) {
         winnerTime = min(motors[i].autoTuneTime, winnerTime);
         maxP = max(motors[i].getKP(), maxP);
     }




     if (maxP >= PmaxLimit) return;
     if (loopCount >= maxLoopCount) return;


     Serial.println("Autotune");
     for (int i = 0; i < motorCount; i++) {
         if (motors[i].autoTuneTime == 0 || motors[i].autoTuneTime > winnerTime)
         {
             float next = motors[i].getKP() + Pstep;
             if (next < PmaxLimit)
                 motors[i].setKP(next);
         }
         Serial.print(motors[i].getKP());
         Serial.print("\t");
     }
     Serial.println();
     goto RACE;
}




Motor::Motor(byte id, bool active, Sabertooth& controller, byte controllerNumber, byte controllerPin, uint16_t potiPin, int rangeMin, int rangeMax, int deadZone, int centerOffSet)
    : _id(id), _active(active), _controller(controller), _controllerNumber(controllerNumber), _controllerPin(controllerPin), 
    _potiPin(potiPin), _rangeMin(rangeMin), _rangeMax(rangeMax), _deadZone(deadZone), 
    _centerOffSet(centerOffSet)
{
    EMA_S = getCurrentPosition();
}


void Motor::enablePID(bool enable) {
    usePID = enable;
}


// position from [-512 - 512],  power range from  [0 - 10]
void Motor::drive(int position, float power) {


    // transform range from [-512 - 512] to [0 - 1024]
    _goToPosition = position + 512 + _centerOffSet;


    // set  min and max positions
    if (_goToPosition < _rangeMin + _centerOffSet) _goToPosition = _rangeMin + _centerOffSet;
    if (_goToPosition > _rangeMax + _centerOffSet) _goToPosition = _rangeMax + _centerOffSet;


    if (!usePID) {
        if (power > 10) power = 10;
        if (power < 0) power = 0;


        _power = power * 12.7;
        if (_currentPosition > _goToPosition) {
            _power *= -1;
        }
        // Power Range check
        _power = constrain(_power, MIN_OUT, MAX_OUT);


        // Deadzone check
        int delta = _currentPosition - _goToPosition;
        if (abs(delta) <= _deadZone)
        {
            _goToPosition = _currentPosition;
            _power = 0;
        }
    }
}


void Motor::update() {
    // check the status
    if (!_active) {
        stop();
        return;
    }


    // range [0 - 1024]
    _currentPosition = getCurrentPosition();


    // sanity check to stop the motor if not in safe range
    if (!checkRange(_currentPosition + _centerOffSet)) {
        stop();
        //return;
    }


    // to PID or not to PID that is ...
    if (usePID) {
        _power = doPID(_goToPosition, _currentPosition);
    }
    else
    {
        // Deadzone check
        int delta = _currentPosition - _goToPosition;
        if (abs(delta) <= _deadZone)
        {
            _goToPosition = _currentPosition;
            _power = 0;
        }
    }


    if (abs(_power)  < 10 ) _power = 0;
    //if (_power > 100) _power = 100;


    // drive the motor
    _controller.motor(_controllerPin, _power);


}


int  Motor::doPID(float setpoint, float currentPostion)
{
    if (setpoint != lastSetpoint || sampleCount == 10) {
        sampleCount = 0;
        Ierror = 0;
        preverror = 0;
        lastSetpoint = setpoint;
    }
    sampleCount++;


    //calculate error values, distance from setpoint to current popsition
    error = (setpoint - currentPostion); 
    
    // deazone check
    if (abs(error) <= _deadZone)
    {
        error = 0;
        return 0;
    }


    //if (preverror == 0)
    //    preverror = error;


    derror = (preverror - error);
    preverror = error; //set previous error to current error


    Ierror += ((error/100) * sampleCount);


    int output = KP*error + KI*Ierror + KD*derror; //PID equations
    // set output limits 
    output = constrain(output, MIN_OUT, MAX_OUT);


    return output;
}


// Motor Params
void Motor::setMin(int value) {
    _rangeMin = value+512;
}


void Motor::setMax(int value) {
    _rangeMax = value+512;
}


void Motor::setDeadZone(int value) {
    _deadZone = value;
}


void Motor::setCenterOffset(int value) {
    _centerOffSet = value;
}


// PID tuning Params
void Motor::setKP(float value) {
    KP = value;
}


void Motor::setKD(float value) {
    KD = value;
}


void Motor::setKI(float value) {
    KI = value;
}


float Motor::getKP() { return KP; }
float Motor::getKD() { return KD; }
float Motor::getKI() { return KI; }






void Motor::stop() {
    _power = 0;
    _controller.motor(_controllerPin, 0);
}


void Motor::standby() {
    bool lastUsePID = usePID;
    usePID = false;
    drive(0, 4);
    usePID = lastUsePID;
}


bool Motor::checkRange(int position) {
    if (position > _rangeMin && position < _rangeMax) {
        return true;
    }
    return false;
}


int Motor::getCurrentPosition() {
    int sensorValue = analogRead(_potiPin);
    EMA_S = (EMA_a*sensorValue) + ((1 - EMA_a)*EMA_S);    //run the EMA
    return (int)EMA_S;
    //return sensorValue;
}




int* Motor::getPositionInfo() {
    static int pos[4];
    pos[0] = _id;
    pos[1] = _goToPosition;
    pos[2] =_currentPosition;
    pos[3] = _power;
    return pos;
}


const char* Motor::toString() {
    static char buf[128];
    sprintf(buf, "Motor: #%d_%d, active: %c, position: %d, goTo: %d, power: %d, Min: %d, Max: %d, Dead: %d, Off: %d,  PID %d, %d, %d", _controllerNumber, _controllerPin, _active ? 'y' : 'n', _currentPosition, _goToPosition, _power,
        _rangeMin, _rangeMax, _deadZone, _centerOffSet,
        (int)error, (int)Ierror, (int)derror);
    return buf;
}
 
U

User 15867

Video iRacing mit Soundtest

Hier ein neues Video mit iRacing auf der NOS, Porsche GT3 Cup im Overlay und einem Soundtest.


Ich bin mal absichtlich über die Cubs geräubert um euch zu demonstrieren, wie sich der Sim bewegt (kleiner Crash inklusive).
Ab der 20. Sekunde habe ich den Sound von iRacing runtergedreht, damit ihr die Mechanik des Sims hören könnt.

:DExtra für Jochen gibt es ganz zum Schluss eine Sequenz, wo das Traction Loss eingreift.
Auf seine Empfehlung hin auf Sway (Roll) gelegt.

Bei der Berechnung für den Drift aus den Telemetry-Daten von iRacing bin ich mir nicht ganz sicher, ob das sauber implementiert ist.

Code:
                        // Porsche GT3 Cup
                        float t1 = VelocityX - YawRate *(4.564f / 2); // Länge // Radstand: 2.456
                        float t2 = VelocityY - YawRate *(1.980f / 2);  // Breite 


                        slipAngel = Math.Atan(t2 / t1) * 180 / 3.141593;

Kennt sich Jemand damit aus und kann mich hier unterstützen?

Fühlt sich aber schon mal ganz gut an.
 
Zuletzt bearbeitet:
U

User 15867

Motor-Hebel

Motor-Hebel

Um die Kraft vom Motor auf das Sitzgestell zu bekommen bietet Simukit CNC-gefräste Motor-Arme/Hebel an.
Diese sind aus Aluminium und haben in 5 mm Abständen kleine Bohrungen als Markierung für die Armlänge von 40 – 70 mm.
An dieser Stelle müsst ihr euch entscheiden, wie lang der Hebel sein soll.
Ein Kurzer Hebel bedeutet viel Kraft, aber wenig Geschwindigkeit. Umgekehrt eine langer Hebel weniger Kraft dafür höhere Geschwindigkeit.
Hier ist also ein Kompromiss gefragt. Ich habe mich für eine Länge von 60 mm entschieden.
Bei einem Bewegungsumfang von 180° wären das 12 cm Bewegungsfreiheit.
Der Motor, in meiner Konstruktion, bewegt sich aber nicht über einen Bereich von 180° sondern in einem deutlich geringeren Bereich ~90° - 120°.

attachment.php


Damit erreiche ich eine ungefähre einen möglichen Stellbereich von 8,5 cm für die Bodenplatte des Sitzes über den gesamten Weg. Das hört sich vielleicht nicht viel an, aber es ist mehr als genug.

Mein Tipp an dieser Stelle, nehmt lieber einen tendenziell kürzeren Hebelarm.
Die Berechnungen sind einfache Trigonometrie und sollten für jede Konstruktion individuell durchgeführt werden.

Einen großen Bewegungsumfang spielt erst dann eine Rolle, wenn mehrere Effekte zusammenkommen.
Z. B. ihr bremst, dabei wird der Sitz nach vorne abgekippt. Wenn dann noch ein weitere Effekt z. B. Pitch, für die Simulation von Berg und Talfahrten dazu kommt, dann kann ist ein großer potentieller Stellbereich von Vorteil. Mit einem kurzen Arm wird unter Umständen einer der Effekte nicht so dargestellt, wie gewünscht, da man den Sitz nicht weiter absenken oder anheben kann.

Entscheidet euch für eine Armlänge und bohrt mit einem 8,5 mm Bohrer (mit Kleinem vorbohren) ein Kernloch an der gewünschten Stelle. Anschließend schneidet ihr ein M10 Gewinde. Für die Befestigung der Gelenkköpfe.
Dafür verwendet ihr einen Gewindeschneider (z. B. https://www.amazon.de/gp/product/B000K2TC7K/ref=oh_aui_detailpage_o06_s00?ie=UTF8&psc=1).
Wenn ihr keine Übung darin habt, so gibt es einen guten Tipp, der auch dem Anfänger ein gutes Ergebnis (gerades Gewinde) ermöglicht. Spannt den Gewindeschneider in die Ständerbohrmaschine ein und dreht mit der Hand die ersten 2-3 Gewindegänge. Das ermöglicht euch einen geraden Ansatz, der sehr wichtig für ein gerades Gewinde ist.

Der 120° Trick

Die Bodenplatte in einem Winkel von 120°, in der neutralen Motorposition, über die Gelenkaugen und Gewindestangen verbunden. Das erhöht die Geschwindigkeit, da bei gleicher Drehbewegung ein größerer Weg zurückgelegt wird.
 
Zuletzt bearbeitet:
U

User 5295

Motor-Hebel

Der 120° Trick

Die Bodenplatte in einem Winkel von 120°, in der neutralen Motorposition, über die Gelenkaugen und Gewindestangen verbunden. Das erhöht die Geschwindigkeit, da bei gleicher Drehbewegung ein größerer Weg zurückgelegt wird.


Kannst du davon ein Bild machen. Ich kann mir das im Moment nicht richtig vorstellen.
 
U

User 15867

Motor-Hebel

Der 120° Trick

Die Bodenplatte in einem Winkel von 120°, in der neutralen Motorposition, über die Gelenkaugen und Gewindestangen verbunden. Das erhöht die Geschwindigkeit, da bei gleicher Drehbewegung ein größerer Weg zurückgelegt wird.


Kannst du davon ein Bild machen. Ich kann mir das im Moment nicht richtig vorstellen.





120.jpg

Klar, bitte schön. Anstatt senkrecht nach oben 90° steht er zur Mitte hin gekippt.
 
U

User 12216

Sorgt hauptsächlich für einen nahezu linearen Weg. Sollte unbedingt so montiert werden

Gesendet von meinem C6833 mit Tapatalk
 
U

User 15867

Video mit Motor-Bewegung und Motor Charts

Hier ein neues Video, dass die Motor Bewegungen von vorne unter dem Sitz zeigt.
Im unteren rechten Bereich seht ihr das Motor Chart von SimMotion. Oben Rechts mich ;)

Motor Chart:

4 Charts für jeden Motor eins, angeordnet wie montiert, aus Sicht des Fahrers.

Rot = Setpoint (Angabe in welche Position der Motor sich bewegen soll)

Blau = Aktuelle Position des Motors (Poti angaben von 0 - 1023)

Gelb = Motorsteuerung (Kraft-Werte von -127 - +127, die vom Arduino an die Sabertooth gegeben werden)

Video stottert ein wenig, sorry.

Wollte euch aber den Einblick liefern.

 
U

User

Danke fürs Video, du hast dir da ne sehr nette Plattform gebaut, Respekt!
 
U

User 10503

@Micha! hast du die Intensität bei Kurvenfahrt sehr niedrig eingestellt? ich bilde mir ein, dass man selbst bei engen Kurven kaum oder zumindest eine sehr geringe Seitenneigung der Plattform erkennen kann. mir fallen hauptsächlich die Höhenbewegungen der Bodenwellen auf. auch beim Bremsen ist die Neigung nach vorne sehr gering.
willst du das so?
 
U

User 15867

Hallo Venti,

gut beobachtet.

Aktuell verwende ich keine Rolleffekte, um die G-Kraft zu simulieren.
Die Straßenlage und die seitliche Beschleunigung schon.
Ich nutzt Roll zusätzlich auch für den Drift.

Meine Software erlaubt unterschiedliche Effekte. Beim Bremsen z. B. nur hinten. Damit ereiche ich ein gedämpfte abwärts Bewegung.
 

Ähnliche Themen

Oben