1. Do not share user accounts! Any account that is shared by another person will be blocked and closed. This means: we will close not only the account that is shared, but also the main account of the user who uses another person's account. We have the ability to detect account sharing, so please do not try to cheat the system. This action will take place on 04/18/2023. Read all forum rules.
    Dismiss Notice
  2. For downloading SimTools plugins you need a Download Package. Get it with virtual coins that you receive for forum activity or Buy Download Package - We have a zero Spam tolerance so read our forum rules first.

    Buy Now a Download Plan!
  3. Do not try to cheat our system and do not post an unnecessary amount of useless posts only to earn credits here. We have a zero spam tolerance policy and this will cause a ban of your user account. Otherwise we wish you a pleasant stay here! Read the forum rules
  4. We have a few rules which you need to read and accept before posting anything here! Following these rules will keep the forum clean and your stay pleasant. Do not follow these rules can lead to permanent exclusion from this website: Read the forum rules.
    Are you a company? Read our company rules

My 2DOF with stepper motors

Discussion in 'DIY Motion Simulator Projects' started by Lebois, Jun 28, 2019.

  1. Lebois

    Lebois (maybe I am wrong, but who knows...)

    Joined:
    Dec 10, 2018
    Messages:
    350
    Occupation:
    Math teacher
    Location:
    France
    Balance:
    2,544Coins
    Ratings:
    +206 / 2 / -0
    My Motion Simulator:
    2DOF
    Hi !

    Here is my 2DOF sim motion rig based on two stepper motors. The goal was to build a rig that would integrate all the features in one, while being fast to start. It will be used for a 24h race this summer. I also wanted something quite compact because I will be moving to a flat. On top of that, I wanted to build as many components by myself. I have been working on the rig for 3 months. Everything needs refinements but it starts to fall into place ! I can share every 3D files, but don't expect it to be perfect. Here is the guided tour :

    [​IMG]

    It's mainly based on a steel structure (20x35 rectangular profile) with two arms at the front. I needed to put them quite far from the pivot to have enough force.
    [​IMG]

    The brain in a Arduino Leonardo. I use a LCD screen to debug everything, calibrate etc...I will put the code below.

    [​IMG]

    Arduino sends pulse and dir signals to two HBS86h stepper drivers. I had a hard time trying to make them work. I learned a lot doing this project, but it also means that it took quite a long time... It's a closed loop driver. that means that an position encoder is fitted on the motor shaft and indicates to the driver where the motor is, and the driver can correct the position.

    [​IMG]

    The motors are two 12Nm Nema 34 stepper motors (ACT MOTOR 34SSM5460-EC1000). I will had a 5:1 gear reducer to have even more torque. For the moment, it's a bit weak, but very very fast.

    [​IMG]

    Then we have the arm. It's a 12mm threaded rod, and I think it's a minimum because there is a lot of torque to support. I know that the wood part seems a bit crappy but it does the job...

    [​IMG]65448580_1352336744918013_1962440655703638016_n

    The pivot is based on a U joint. It's fitted in 3D printed PLA parts to be sure that there is no play. Then it's reinforced with steel parts.

    That's it for the motion part. Now with have the indispensable steering wheel and pedals, and additional features :

    [​IMG]

    I modified a wheel by LeecarL. I added some buttons, redesigned the paddles to be better fitted to my hands, added two of them in the back because I need it for KERS (I drive the toyota ts050). I also used the bluhid board, but it doesn't seem to work anymore, so I will be using the Simucube wireless board... Thanks a lot to LeecarL, his work saved me a lot of time.
    https://www.thingiverse.com/thing:3468336


    [​IMG]

    The motor is a Mige 80ST-M04025. I choosed it because it lighter that common DD motors. The big problem is that it's overheating a lot. I had a MIGE 130ST-M10010 before. For Project cars 2, 55% torque was already very hard, and the 130ST didn't heat. The equivalent of maybe 40-45% for 130st torque with 80ST will be too hot to put your hands on it... So I added this box with a fan. I am printing the last part now, we will see if it's efficient. By the way, I thought that because the 80st is faster (2500 rpm insted of 1000 rpm for 130st), and have less inertia, it would be better (more details?). In fact, it's not really better. It's just far more efficient to cut your finguers when there is a bug... If I should do it again, maybe I would go for a 90ST-M04025, to avoid heat problems...Otherwise, the 80ST does have enough torque IMO and is very light.

    [​IMG]

    The accelerator pedal is 3D printed a lot. It's tunable, and there is no flex. It uses a simple potentiometer (my feet doesn't have a 16 bits resolution^^). It does the job. If I have more time, I would like to 3D print the metal arm to have something that everyone can print...

    EDIT : Now everything is 3D printeable, so anyone with a 3D printer can afford the best pedal set for around 150€. The full project is here.
    [​IMG]

    The brake pedal is hydraulic with an hydraulic pressure sensor. It works perfectly, even with 3D printed parts. There is an hydraulic caliper at the end.

    And now the features :

    [​IMG]

    I use 4 bass shakers on the rig. So I 3D printed some parts to build a component that integrates the amplifiers and can be installed very fast.
    [​IMG]

    There is a 4 points seat belt. The motor pull it back when we brake.

    And that's it !

    Now the arduino code.
    Code:
    // Inspired by Sirnoname
    
    int const StepPin1 = 2, DirPin1 = 3, StepPin2 = 4, DirPin2 = 5;  //Step or pulse pin
    int buffer           =  0 ;    // It takes the value of the serial data
    int buffercount      = -1 ;    // To count where we are in the serial datas
    int commandbuffer[4] = {0};    // To stock the serial datas in the good order.
    float nbrStep1 = 0, nbrStep2 = 0; // The number of pulse that each motor will receive. If the number is negative, it inverts the direction
    int pitchTarget = 511 ; // The pitch position we want to reach
    int rollTarget = 511;   // The roll position we want to reach
    int maxPitch = 1022, maxRoll = 1022, maxRadius = 2000; //set the limits
    float pitchPosition = 511, rollPosition = 511; // The actual positions
    int c = 0;
    int dir1 = 1;   //Will be used to set the motors direction
    int dir2 = 1;
    int pulseWidth = 15;
    bool LCDMode = true;
    
    
    #include <LiquidCrystal.h> // includes the LiquidCrystal Library
    
    LiquidCrystal lcd(7, 12, 8, 9, 10, 11); // Creates an LC object. Parameters: (rs, enable, d4, d5, d6, d7)
    
    void setup() {
      Serial.begin(115200);
      pinMode(StepPin1, OUTPUT);
      pinMode(DirPin1, OUTPUT);
      pinMode(StepPin2, OUTPUT);
      pinMode(DirPin2, OUTPUT);
      if (LCDMode) {
        lcd.begin(16, 2);
        Start();
      }
    }
    
    void loop() {
      SerialReader();                             //Get the datas from Simtools
      LimitManager();                             //See if the positions are reachable
      CommandWorker();                            //Convert the position targets to pulse number
      MoveSteppers(nbrStep1, nbrStep2);           //Set directions and send the pulses.
    
    }
    
    void MoveSteppers(float step1, float step2) {
      if ((step1 != 0 ) && (step2 != 0)) {
        DirectionManager(step1,step2); //put the motors in the right directions
        if (abs(step1) == abs(step2)) {
          if (dir1 == dir2) { //if pitch only...
            for (int i = 0; i < abs(step1); i++) {
              doublePulse(StepPin1, StepPin2);
              pitchPosition += dir1;
            }
          }
          else { //if roll only
            for (int i = 0; i < abs(step1); i++) {
              doublePulse(StepPin1, StepPin2);
              rollPosition += dir1;
            }
          }
        }
        else {
    
          for (int i = 0; i < min(abs(step1), abs(step2)); i++) {
            doublePulse(StepPin1, StepPin2);
            pitchPosition += (dir1 + dir2)/2 ;
            rollPosition += (dir1 - dir2)/2;
          }
    
          if (max(abs(step1), abs(step2)) == step1) {
            for (int i = 0; i < abs(step1) - abs(step2); i++) {
              monoPulse(StepPin1);
              rollPosition += dir1/2;
            }
          }
          else {
            for (int i = 0; i < abs(step2) - abs(step1); i++) {
              monoPulse(StepPin2);
              rollPosition += dir2/2;
            }
          }
        }
      }
    }
    
    // Simtools output : P<Axis1a><Axis2a>
    
    
    
    void CommandWorker() {
      nbrStep1 = pitchTarget - pitchPosition - rollPosition + rollTarget;
      nbrStep2 = pitchTarget - pitchPosition + rollPosition - rollTarget;
    if (nbrStep1 > c){c = nbrStep1;}
      if (LCDMode) {
        LCD();
      }
    }
    
    void CommandWorkerPitchOnly() {
      c++;
      nbrStep1 = pitchTarget - pitchPosition ;
      nbrStep2 = pitchTarget - pitchPosition ;
    
      if (LCDMode && c == 200) {
        LCDPitch();
        c = 0;
      }
    }
    void CommandWorkerRollOnly() {
      c++;
      /*    if(abs(rollTarget - rollPosition ) < 1) {
            nbrStep1=0;
            nbrStep2=0;
          }
          else{*/
      nbrStep1 = rollTarget - rollPosition;
      nbrStep2 = rollPosition - rollTarget;
    
    
      if (LCDMode && c == 200) {
        LCDRoll();
        c = 0;
    
      }
    }
    
    void Start() {
      lcd.setCursor(0, 0);
      lcd.print("Waiting for datas...");
      while (!Serial.available()) {
        delay(100);
      }
      lcd.setCursor(0, 0);
      lcd.print("Datas received !");
      lcd.setCursor(1, 0);
      lcd.print("Starting in :");
      delay(1000);
      lcd.clear();
      lcd.print("Get ready.");
      for (int i = 0; i < 1; i++) {
        lcd.setCursor(0, 0);
        lcd.clear();
        lcd.print("Get ready.");
        lcd.setCursor(1, 7);
        lcd.print(3 - i);
        for (int j = 0; j < 3; j++) {
          lcd.setCursor(6 - j, 1);
          lcd.print("*");
          lcd.setCursor(8 + j, 1);
          lcd.print("*");
          delay(333);
        }
        lcd.clear();
      }
    }
    
    void LCD() {
      lcd.clear();
      lcd.setCursor(0, 0);
      lcd.print("P ");
      lcd.print(pitchPosition);
      lcd.print("R ");
      lcd.print(rollPosition);
      lcd.setCursor(0, 1);
      lcd.print(c);
     // lcd.print(" M2 ");
    //  lcd.print(nbrStep2);
    }
    
    void LCDPitch() {
      lcd.clear();
      lcd.setCursor(0, 0);
      lcd.print("Pi. ");
      lcd.print(pitchPosition);
      lcd.setCursor(0, 1);
      lcd.print("M1 ");
      lcd.print(nbrStep1);
      lcd.print(" M2 ");
      lcd.print(nbrStep2);
    }
    void LCDRoll() {
      lcd.clear();
      lcd.setCursor(0, 0);
      lcd.print("Ro. ");
      lcd.print(rollPosition);
      lcd.setCursor(0, 1);
      lcd.print("M1 ");
      lcd.print(nbrStep1);
      lcd.print(" M2 ");
      lcd.print(nbrStep2);
    }
    
    void End() {
      lcd.clear();
      lcd.print("No more datas !");
      delay(2000);
      lcd.setCursor(0, 1);
      lcd.print("No more fun...");
      delay(4000);
      while (1 == 1) {
        delay(500);
      }
    }
    
    void monoPulse(int stepPin) {
      digitalWrite(stepPin, HIGH);
      delayMicroseconds(pulseWidth);
      digitalWrite(stepPin, LOW);
      delayMicroseconds(pulseWidth);
    }
    
    void doublePulse(int StepPin1, int StepPin2) {
      digitalWrite(StepPin1, HIGH);
      digitalWrite(StepPin2, HIGH);
      delayMicroseconds(pulseWidth);
      digitalWrite(StepPin1, LOW);
      digitalWrite(StepPin2, LOW);
      delayMicroseconds(pulseWidth);
    }
    
    void SerialReader() {
    
      while (Serial.available())
      {
        if (buffercount == -1)
        {
          buffer = Serial.read();
          if (buffer != 'P') {
            buffercount = -1; // "P" is the marquer. If we read P, the next data is pitch
          } else {
            buffercount = 0;
          }
        }
        else   //  if(buffercount>=0)
        {
          buffer = Serial.read();
          commandbuffer[buffercount] = buffer; // The first value next to "P" is saved in commandbuffer in the place "buffercount"
          buffercount++;
          if (buffercount > 3)
          {
            pitchTarget = (commandbuffer[0]) * 256 + commandbuffer[1];
            rollTarget = (commandbuffer[2]) * 256 + commandbuffer[3];
            buffercount = -1; // Re-initialize buffercount.
            break;
          }
        }
      }
    }
    
    void LimitManager(){
     
    /*if ( (rollTarget*maxRoll/1022)^2 + (pitchTarget*maxPitch/1022)^2 > (maxRadius)^2)  {
        pitchTarget = pitchPosition; //If roll+pitch exceed limits, don't pitch
      }
    
    if ( (rollTarget*maxRoll/1000)^2 + (pitchTarget*maxPitch/1000)^2 > (maxRadius)^2)  { //If there is too much roll, don't roll more
        rollTarget = rollPosition; //If roll+pitch still exceed limits, don't roll neither
      }*/
      if (abs(rollTarget-511) > 40){rollTarget=rollPosition;}
      if (abs(pitchTarget-511) > 25){pitchTarget=pitchPosition;}
    }
    
    
    void DirectionManager(float step1, float step2){
        if (step1 < 0) { //dirPin1 = 3
          digitalWrite(DirPin1, LOW);
          //PORTD &= B00001000;
          dir1 = -1;
        }
        else {
          digitalWrite(DirPin1, HIGH); //
          //PORTD |= B00001000;
          dir1 = 1;
        }
    
        if (step2 < 0) {
          digitalWrite(DirPin2, LOW);
          dir2 = -1;
        }
        else {
          digitalWrite(DirPin2, HIGH);
          dir2 = 1;
        }
        delayMicroseconds(5);
    }
    EDIT : I play in VR (Pimax 5k+), so if it's ugly, I don't care.

    Attached Files:

    • Like Like x 7
    • Agree Agree x 1
    • Winner Winner x 1
    • Creative Creative x 1
    Last edited: Jul 10, 2020
  2. Lebois

    Lebois (maybe I am wrong, but who knows...)

    Joined:
    Dec 10, 2018
    Messages:
    350
    Occupation:
    Math teacher
    Location:
    France
    Balance:
    2,544Coins
    Ratings:
    +206 / 2 / -0
    My Motion Simulator:
    2DOF
    Update #1 :
    - I made a first test un VR on Spa, and it's just crazy. There were a lot of bugs, but I think that it will be awesome !!!

    - I added Steel parts to add rigidity to the pivot and i am preparing some parts for painting.

    - I modified the code to be a closed loop. The position will be given by a gyroscope. It's really needed because the position becomes wrong if the rig is moving too quickly. There is now also a calibration at start up.

    - The motor is now well cooled. I will add heatsinks to improve cooling, but it's already ok at around 16A.

    - I received this quick release, and built it yesterday. I added a small 3D printed ring, and there is now almost no play at all, very satisfied.
    • Like Like x 1
    Last edited: Sep 2, 2020
  3. Lebois

    Lebois (maybe I am wrong, but who knows...)

    Joined:
    Dec 10, 2018
    Messages:
    350
    Occupation:
    Math teacher
    Location:
    France
    Balance:
    2,544Coins
    Ratings:
    +206 / 2 / -0
    My Motion Simulator:
    2DOF
    UPDATE #2 :

    The gyroscope gy-521 can't be used at high rates... For a reason I don't know it freezes randomly if used with no delay on a board that do other things between readings. After a lot of research, the best solution I found was to implement a fonction that reset the board if it detects a freeze, and that's not acceptable for a motion system.
    I will try to retrieve the signal coming out of the stepper encoder directly, and then send this position by I2C to the main arduino.
  4. Erik Middeldorp

    Erik Middeldorp Member

    Joined:
    Sep 15, 2018
    Messages:
    50
    Occupation:
    sheet metal worker
    Location:
    Auckland, New Zealand
    Balance:
    606Coins
    Ratings:
    +45 / 0 / -0
    were you using Jeff Rowberg's MPU6050 library and the DMP or were you accessing the raw values?
    I'm hoping to use a gyro for a 2 dof rig, I'm just testing it with a small model at the moment and trying to figure out the arduino code. I was having issues using the MPU6050 library and the DMP. If I understand it right, the DMP stores values in a buffer that the arduino can read from but if it's not read from regularly enough, it overflows and you need to reset the gyro... was that your problem?
    I tried modifying the balancing robot code from here https://www.instructables.com/id/Arduino-Balance-Balancing-Robot-How-to-Make/ which reduced the 'fifo overflow' errors but I still haven't got it to work right. I'm now thinking to follow the approach in this series of tutorials for a quadcopter which accesses the gyro and accelerometer readings directly and processes them on the arduino rather than the DMP. It seems to me like his method of having the acceleration input just strong enough to prevent drift is better than what the DMP does which lets the acceleration data influence the rotation more strongly and make it more susceptible to vibration/acceleration. Also I think accessing the gyro/accelerometer data directly means it doesn't get put in a buffer and so doesn't overflow... but I could be wrong there. Still trying to figure this out.
  5. Lebois

    Lebois (maybe I am wrong, but who knows...)

    Joined:
    Dec 10, 2018
    Messages:
    350
    Occupation:
    Math teacher
    Location:
    France
    Balance:
    2,544Coins
    Ratings:
    +206 / 2 / -0
    My Motion Simulator:
    2DOF
    I use the wire.h library and the following classic code :

    1. Wire.beginTransmission(MPU_addr);
    2. Wire.write(0x3B); // starting with register 0x3B (ACCEL_XOUT_H)
    3. Wire.endTransmission(false);
    4. Wire.requestFrom(MPU_addr,14,true); // request a total of 14 registers
    5. AcX=Wire.read()<<8|Wire.read(); // 0x3B (ACCEL_XOUT_H) & 0x3C (ACCEL_XOUT_L)
    6. AcY=Wire.read()<<8|Wire.read(); // 0x3D (ACCEL_YOUT_H) & 0x3E (ACCEL_YOUT_L)
    7. AcZ=Wire.read()<<8|Wire.read(); // 0x3F (ACCEL_ZOUT_H) & 0x40 (ACCEL_ZOUT_L)
    8. Tmp=Wire.read()<<8|Wire.read(); // 0x41 (TEMP_OUT_H) & 0x42 (TEMP_OUT_L)
    9. GyX=Wire.read()<<8|Wire.read(); // 0x43 (GYRO_XOUT_H) & 0x44 (GYRO_XOUT_L)
    10. GyY=Wire.read()<<8|Wire.read(); // 0x45 (GYRO_YOUT_H) & 0x46 (GYRO_YOUT_L)
    11. GyZ=Wire.read()<<8|Wire.read(); // 0x47 (GYRO_ZOUT_H) & 0x48 (GYRO_ZOUT_L)

    While debugging, I discovered that it freezes randomly after the wirerequest...
  6. Lebois

    Lebois (maybe I am wrong, but who knows...)

    Joined:
    Dec 10, 2018
    Messages:
    350
    Occupation:
    Math teacher
    Location:
    France
    Balance:
    2,544Coins
    Ratings:
    +206 / 2 / -0
    My Motion Simulator:
    2DOF
    UPDATE #3.

    1) As I was waiting for parts, I tought it was a good moment to start to paint. I thought I could just do a quick preparation job and just enjoy painting... I was definitely wrong... So I messed up most of the parts. The color is blue. It's my neighboor's car color. I liked it (the car...), so I painted my MTB with the same color, and now my rig. Everything is logical.^^
    [​IMG]

    [​IMG]
    As you can see, it needed more sanding... I didn't clear coated it as it needs to be redone.

    [​IMG]
    Here the primer wasn't evenly applied..


    2) As the gyro get us nowhere, I tried to retrieve the position of the motors directly from the optical rotary encoder. It worked quickly, but for whatever reason, it starts to offset with time... I will try to find another gyro later...

    3) I put a 3D plastic fan case around the motor, and added big heat sinks. Now the motor is really well cooled. I can even run it just with the heat sinks, without the plastic fan case. But now it's the wires that overheat... So I will keep the motor at around 17A, that is already plenty enough.
    [​IMG]

    [​IMG]

    4) I installed this quick release and I am really satisfied. I added a small 3D printed part to be sure that there is no play, but it's already satisfying without it.

    [​IMG]

    I don't know if it is a good idea to put alloy screws, as they are weaker than steel ones but... it's red !!!

    5) I am installing the Simucube wireless adapter.
    [​IMG]

    Before that I managed to quickly mount the G27 shifter.
    [​IMG]

    6) I mounted a 5:1 gear reducer. So I will have 5 times more torque but with less speed. It also adds "resolution". I am waiting for the 16mm coupler to land. I am printing a plastic one, but I am not sure that it will handle the torque...
    [​IMG]
    • Like Like x 3
  7. Lebois

    Lebois (maybe I am wrong, but who knows...)

    Joined:
    Dec 10, 2018
    Messages:
    350
    Occupation:
    Math teacher
    Location:
    France
    Balance:
    2,544Coins
    Ratings:
    +206 / 2 / -0
    My Motion Simulator:
    2DOF
    UPDATE #4

    I 3D printed coupler to link the motors to the rig. It seems to handle the torque, but maybe not for long (EDIT : there is a play that keeps increasing, ok for testing but it makes the motion vague)...

    I tested separetly roll and pitch, it works great, here is a video with pitch only.

    With pitch and Roll together it works too but I need more tuning.
    • Like Like x 1
    Last edited: Jul 14, 2019
  8. Lebois

    Lebois (maybe I am wrong, but who knows...)

    Joined:
    Dec 10, 2018
    Messages:
    350
    Occupation:
    Math teacher
    Location:
    France
    Balance:
    2,544Coins
    Ratings:
    +206 / 2 / -0
    My Motion Simulator:
    2DOF
    UPDATE #5

    The steppers are very loisy and generates a lot of vibrations... Depending of the setup, I might have to use a headphone, as the noise from the motors covers the sound coming from my screen. I am working on making microstepping work.
    The positive point is that it is very accurate while having a lot of power. You can really feel every detail of the track, and when there is a big bump or kerb...well...you feel it right ^^.
  9. Lebois

    Lebois (maybe I am wrong, but who knows...)

    Joined:
    Dec 10, 2018
    Messages:
    350
    Occupation:
    Math teacher
    Location:
    France
    Balance:
    2,544Coins
    Ratings:
    +206 / 2 / -0
    My Motion Simulator:
    2DOF
    UPDATE #6

    I spent a lot of time trying to make microstepping work. I found out that my uno board was defective... Now it works. Again, it needs a lot more tuning, but at least it's smooth and quiet enough.

    Here is a video.
    • Like Like x 1
    • Creative Creative x 1
    Last edited: Jul 21, 2019
  10. Lebois

    Lebois (maybe I am wrong, but who knows...)

    Joined:
    Dec 10, 2018
    Messages:
    350
    Occupation:
    Math teacher
    Location:
    France
    Balance:
    2,544Coins
    Ratings:
    +206 / 2 / -0
    My Motion Simulator:
    2DOF
    Code:
    // Stepper microstepping settings :  51200
    
    int const StepPin1 = 2, DirPin1 = 3, StepPin2 = 4, DirPin2 = 5;  //Step or pulse pin, check how DirectionManager is written.
    int commandbuffer[4] = {0};    // To stock the serial datas in the good order.
    int nbrStep1 = 0, nbrStep2 = 0; // The number of pulse that each motor will receive. If the number is negative, it inverts the direction
    unsigned int pitchTarget = 16384, rollTarget = 16384;  // The pitch and roll positions we want to reach
    unsigned int  pitchPosition = 16384, rollPosition = 16384; // The actual positions
    int c = 0;
    int dir1 = 1, dir2 = 1;         //Will be used to set the motors direction
    int pulseWidth = 10;            //From the HBS86h datasheet : For reliable response, pulse width should be longer than 10μs
    bool LCDMode = false, InitializeMode = true;
    int max = 0;
    int x, y;
    int btnPin = 13;
    
    #include <LiquidCrystal.h> // includes the LiquidCrystal Library
    LiquidCrystal lcd(6, 7, 8, 9, 10, 11); // Creates an LC object. Parameters: (rs, enable, d4, d5, d6, d7)
    
    void setup() {
      delay(2000);    //safety delay to flash the code
      pinMode(StepPin1, OUTPUT);
      pinMode(DirPin1, OUTPUT);
      pinMode(StepPin2, OUTPUT);
      pinMode(DirPin2, OUTPUT);
        if (InitializeMode) {
          Initialize();
        }
      Serial.begin(115200);    //To communicate with Simtools
      if (LCDMode) {
        Start();
      }
    
    }
    
    void loop() {
      SerialReader();                             //Get the datas from Simtools
     // LimitManager();
      CommandWorker();                        //Convert the position targets to pulse number
      MoveSteppers(nbrStep1, nbrStep2);           //Set directions and send the pulses.
    }
    
    void MoveSteppers(int step1, int step2) {
    
      DirectionManager(step1, step2); //put the motors in the right directions
      pitchPosition = pitchTarget;
      rollPosition = rollTarget;
      x = abs(step1);
      y = abs(step2);
      for (int i = 0; i < min(x, y); i++) { //Common moves
        doublePulse(StepPin1, StepPin2);
      }
    
      if (max(x, y) == x) {          //Now only M1 will move
        for (int i = 0; i < x - y; i++) {
          monoPulse(StepPin1);
        }
      }
      else {                                                      //Now only M2 will move
        for (int i = 0; i < y - x; i++) {
          monoPulse(StepPin2);
        }
      }
    
    
    }
    
    void CommandWorker() {
      nbrStep1 = pitchTarget - pitchPosition - rollPosition + rollTarget;
      nbrStep2 = pitchTarget - pitchPosition + rollPosition - rollTarget;
      c++;
      if (c > 200) {
        if (LCDMode) {
          LCD();
          c = 0;
        }
      }
    }
    
    void Start() {
      lcd.begin(16, 2);
      lcd.setCursor(0, 0);
      lcd.print("Waiting for");
      lcd.setCursor(0, 1);
      lcd.print("datas...");
      while (!Serial.available()) {     // We wait for serial datas to really start
        delay(50);
      }
      lcd.setCursor(0, 0);
      lcd.print("Datas received !");
      lcd.setCursor(1, 0);
      lcd.print("Starting in :");
      delay(1000);
      lcd.clear();
      lcd.print("Get ready.");
      for (int i = 0; i < 1; i++) {
        lcd.setCursor(0, 0);
        lcd.clear();
        lcd.print("Get ready.");
        lcd.setCursor(1, 7);
        lcd.print(3 - i);
        for (int j = 0; j < 3; j++) {
          lcd.setCursor(6 - j, 1);
          lcd.print("*");
          lcd.setCursor(8 + j, 1);
          lcd.print("*");
          delay(333);
        }
        lcd.clear();
      }
    }
    
    void LCD() {
      lcd.clear();
      lcd.setCursor(0, 0);
      lcd.print("P ");
      lcd.print(pitchTarget);
      lcd.print("R ");
      lcd.print(rollTarget);
      lcd.setCursor(0, 1);
      lcd.print(max);
      // lcd.print(" M2 ");
      //  lcd.print(nbrStep2);
    }
    
    void monoPulse(int stepPin) {
      digitalWrite(stepPin, HIGH);
      delayMicroseconds(pulseWidth);
      digitalWrite(stepPin, LOW);
      delayMicroseconds(pulseWidth);
    }
    
    void doublePulse(int StepPin1, int StepPin2) {
      digitalWrite(StepPin1, HIGH);
      digitalWrite(StepPin2, HIGH);
      delayMicroseconds(pulseWidth);
      digitalWrite(StepPin1, LOW);
      digitalWrite(StepPin2, LOW);
      delayMicroseconds(pulseWidth);
    }
    
    void SerialReader() {       // This function is the work of Sirnoname
      // Simtools output : P<Axis1a><Axis2a>, Data bits : 15 bits, Parity : None, stop bits : 1
      int buffer           =  0 ;    // It takes the value of the serial data
      int buffercount      = -1 ;    // To count where we are in the serial datas
    
      while (Serial.available())
      {
        if (buffercount == -1)
        {
          buffer = Serial.read();
          if (buffer != 'P') {
            buffercount = -1; // "P" is the marquer. If we read P, the next data is pitch
          } else {
            buffercount = 0;
          }
        }
        else   //  if(buffercount>=0)
        {
          buffer = Serial.read();
          commandbuffer[buffercount] = buffer; // The first value next to "P" is saved in commandbuffer in the place "buffercount"
          buffercount++;
          if (buffercount > 3)
          {
            pitchTarget = (commandbuffer[0]) * 256 + commandbuffer[1];
            rollTarget = (commandbuffer[2]) * 256 + commandbuffer[3];
            buffercount = -1; // Re-initialize buffercount.
            break;
          }
        }
      }
    }
    
    void DirectionManager(int step1, int step2) {
      if (step1 < 0) {                //dirPin1 = 3
      digitalWrite(DirPin1, LOW);
       // PORTD &= B11110111;        // This code is faster than ;
        dir1 = -1;
      }
      else {
        digitalWrite(DirPin1, HIGH); //
       // PORTD |= B00001000;
        dir1 = 1;
      }
    
      if (step2 < 0) {
        digitalWrite(DirPin2, LOW);
        dir2 = -1;
      }
      else {
        digitalWrite(DirPin2, HIGH);
        dir2 = 1;
      }
      delayMicroseconds(5);
    }
    
    void LimitManager() {
    
      if (pitchTarget < 1000  || pitchTarget > 27000) {
        pitchTarget = pitchPosition;
      }
      if (rollTarget < 1000  || rollTarget > 27000 ) {
        rollTarget = rollPosition;
      }
    }
    
    void Initialize() {   // Use this function to move the rig at the right place at start up.
      MoveSteppers(-100, 100);
      delay(1000);
      MoveSteppers(100, -100);
        delay(1000);
      while(analogRead(btnPin)==HIGH){
        MoveSteppers(-100, -100);
      //  delay(1);         //Change the delay to tune speed
      }
      pitchPosition = 16384;
      rollPosition = 16384;
      delay(1000);
    
      }
    UPDATE #7

    Ok it works fine now. A new version of the code is available at the end.

    - I added a removable bottle cage : next week we are going to race the 24h of le Mans and are expecting high temperatures.

    [​IMG]
    - I added moving seatbelt
    [​IMG]

    [​IMG]

    - Some parts have been printed again with some improvements, particulary for the pedals :

    [​IMG]
    • Like Like x 2
  11. Lebois

    Lebois (maybe I am wrong, but who knows...)

    Joined:
    Dec 10, 2018
    Messages:
    350
    Occupation:
    Math teacher
    Location:
    France
    Balance:
    2,544Coins
    Ratings:
    +206 / 2 / -0
    My Motion Simulator:
    2DOF
    UPDATE#8

    The seat belt has been upgraded with a cable drum. There is many advantages over an arm style system :
    - more torque because there is relatively less arm
    - no need to adjust the seatbelt, it will automatically be tight in few brakes.

    Here is a video.
  12. Lebois

    Lebois (maybe I am wrong, but who knows...)

    Joined:
    Dec 10, 2018
    Messages:
    350
    Occupation:
    Math teacher
    Location:
    France
    Balance:
    2,544Coins
    Ratings:
    +206 / 2 / -0
    My Motion Simulator:
    2DOF
    I raced the 24h of le mans with two friends. You can find pictures and videos here on the Pimax forum.
    Regarding the simulator :
    - The stepper motors were slowly losing calibration, going a bit down. We re-calibrated them twice during the race... I don't know why.
    - Most of the shifters paddles failed, due to the changes to the design I made... I fixed them during the race.
    - One of the belt tensioner 3D printed part failed, probably because the heated motor soften it... I need to build it with tighter tolerances.
    • Like Like x 2
  13. iLLuac4

    iLLuac4 Active Member

    Joined:
    Jun 19, 2019
    Messages:
    127
    Balance:
    865Coins
    Ratings:
    +52 / 0 / -0
    My Motion Simulator:
    2DOF
    Hi can you please share stl or. model of the arm for motor shaft? TNX
  14. Lebois

    Lebois (maybe I am wrong, but who knows...)

    Joined:
    Dec 10, 2018
    Messages:
    350
    Occupation:
    Math teacher
    Location:
    France
    Balance:
    2,544Coins
    Ratings:
    +206 / 2 / -0
    My Motion Simulator:
    2DOF
    You mean for the seat belt ?
  15. iLLuac4

    iLLuac4 Active Member

    Joined:
    Jun 19, 2019
    Messages:
    127
    Balance:
    865Coins
    Ratings:
    +52 / 0 / -0
    My Motion Simulator:
    2DOF
    yes. If possible original file even better than stl.
  16. Lebois

    Lebois (maybe I am wrong, but who knows...)

    Joined:
    Dec 10, 2018
    Messages:
    350
    Occupation:
    Math teacher
    Location:
    France
    Balance:
    2,544Coins
    Ratings:
    +206 / 2 / -0
    My Motion Simulator:
    2DOF
    The system doesn't allow to upload 3D files :( Can you pm your email to me ?

    Improvements needed :
    We used a potentiometer to tune the preload on the belt. This way the belt was already a bit tensioned before the start of the braking. Otherwise, it means that the motor always runs a bit, so it's always hot, and with times, it weakens the 3D printed parts. This system should be replaced by a spring.

    More informations on my website here.

    I used this parts :
    bicycle brake cable

    motor, quite powerfull, if you have a proper build, it will even be too much powerfull. Maybe around 250W would be better... the thread is inverted so don't loose the bolt.

    driver, as the motor is really powerfull, it needed a strong driver too. I tried other that burned immediately. Plus this one got stop and dir switches that are usefull to tweak the system.

    Pay attention to the fact that the potentiometer wires colors are wrong. It's something like :

    black is signal (you want to replace it with a analog signal coming from an arduino/simhub, you need to convert the pwm signal with a DAC circuit (a capacitor and a resistor, google it),

    red is ground (link it to the arduino ground)

    yellow the VCC... I lost a lot of time because of this...
    Last edited: Sep 2, 2020
  17. noorbeast

    noorbeast VR Tassie Devil Staff Member Moderator Race Director

    Joined:
    Jul 13, 2014
    Messages:
    20,435
    Occupation:
    Innovative tech specialist for NGOs
    Location:
    St Helens, Tasmania, Australia
    Balance:
    144,440Coins
    Ratings:
    +10,730 / 52 / -2
    My Motion Simulator:
    3DOF, DC motor, JRK
    Zip the files and you should be able to upload them, as long as they don't exceed the system max upload file size.
  18. Lebois

    Lebois (maybe I am wrong, but who knows...)

    Joined:
    Dec 10, 2018
    Messages:
    350
    Occupation:
    Math teacher
    Location:
    France
    Balance:
    2,544Coins
    Ratings:
    +206 / 2 / -0
    My Motion Simulator:
    2DOF
    Thanks ! I uploaded two versions :
    - arm/ lever style (v4)
    - rewinder style (v5)

    Attached Files:

    • Like Like x 1
  19. Trigen

    Trigen Active Member

    Joined:
    Nov 25, 2018
    Messages:
    472
    Balance:
    2,826Coins
    Ratings:
    +176 / 1 / -0
    My Motion Simulator:
    2DOF, 3DOF, DC motor, Arduino
    Looks like a great project and its fantastic that you are sharing your stepper code! Big props!
    • Like Like x 1
    • Agree Agree x 1
  20. Lebois

    Lebois (maybe I am wrong, but who knows...)

    Joined:
    Dec 10, 2018
    Messages:
    350
    Occupation:
    Math teacher
    Location:
    France
    Balance:
    2,544Coins
    Ratings:
    +206 / 2 / -0
    My Motion Simulator:
    2DOF
    That project has been made possible because some persons shared their works, now I think it's normal to share mine so everybody can progress :)

    I am currently working on taking away every specific metal part on my pedals so everyone could print it and build it :
    [​IMG]
    • Like Like x 1