1. Hundreds of coffees, endless nights of debugging and coding, and countless feedback by our beta testers led to this new major release. SimTools 2.4 is probably the version with the most upgrades and improvements in a single release ever. Look at everything Dustin has included:
    SimTools 2.4 all features.
    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 Download Package Now!
  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 here. Do not following these rules will lead to permanent exclusion from this website: Read the forum 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...) Gold Contributor

    Joined:
    Dec 10, 2018
    Messages:
    51
    Occupation:
    Math teacher
    Location:
    France
    Balance:
    376Coins
    Ratings:
    +14 / 0 / -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.[/URL] If I have more time, I would like to 3D print the metal arm to have something that everyone can print...
    [​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.
    • Like Like x 3
  2. Lebois

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

    Joined:
    Dec 10, 2018
    Messages:
    51
    Occupation:
    Math teacher
    Location:
    France
    Balance:
    376Coins
    Ratings:
    +14 / 0 / -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
  3. Lebois

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

    Joined:
    Dec 10, 2018
    Messages:
    51
    Occupation:
    Math teacher
    Location:
    France
    Balance:
    376Coins
    Ratings:
    +14 / 0 / -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 New Member

    Joined:
    Sep 15, 2018
    Messages:
    7
    Occupation:
    sheet metal worker
    Location:
    Auckland, New Zealand
    Balance:
    77Coins
    Ratings:
    +0 / 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...) Gold Contributor

    Joined:
    Dec 10, 2018
    Messages:
    51
    Occupation:
    Math teacher
    Location:
    France
    Balance:
    376Coins
    Ratings:
    +14 / 0 / -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...) Gold Contributor

    Joined:
    Dec 10, 2018
    Messages:
    51
    Occupation:
    Math teacher
    Location:
    France
    Balance:
    376Coins
    Ratings:
    +14 / 0 / -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 2
  7. Lebois

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

    Joined:
    Dec 10, 2018
    Messages:
    51
    Occupation:
    Math teacher
    Location:
    France
    Balance:
    376Coins
    Ratings:
    +14 / 0 / -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 at 20:44