AZURIT FULL ODOMETRY MOUVEMENT CONTROL

bernard

Well-known member
Hi all
I finish my test with the Odometry control on Azurit and here how it's work.

Main states are now control in distance move with accel and brake on left and right Wheel.
The Accel is linear and timed control (the sinus compute work also but not better)
The brake is sinus compute and Odometry control to reach a cible.

Into each one shot state there is now:
The accel and brake boolean for the 2 wheels.
The speed and direction of each Wheel.
The Odometry cible for each Wheel.

Here a sample of two states (I replace all the If, ElseIf by a switch case because it's more clear for this parts of code and easier to find the one shot and permanent test corresponding)


Code:
case STATE_PERI_OUT_REV: //in normal mowing reverse after the wire trigger
      if (rollDir == RIGHT) {
        UseAccelLeft = 1;
        UseBrakeLeft = 1;
        UseAccelRight = 1;
        UseBrakeRight = 0;
      }
      else
      {
        UseAccelLeft = 1;
        UseBrakeLeft = 0;
        UseAccelRight = 1;
        UseBrakeRight = 1;
      }
      motorLeftSpeedRpmSet = motorRightSpeedRpmSet = -motorSpeedMaxRpm;
      stateEndOdometryRight = odometryRight - (odometryTicksPerCm * DistPeriOutRev);
      stateEndOdometryLeft = odometryLeft - (odometryTicksPerCm * DistPeriOutRev);
      OdoRampCompute();
      break;

    case STATE_PERI_OUT_ROLL: //roll left or right in normal mode
      AngleRotate = random(motorRollDegMin, motorRollDegMax);
      Tempovar = 36000 / AngleRotate; //need a value*100 for integer division later
      if (dir == RIGHT) {
        UseAccelLeft = 1;
        UseBrakeLeft = 0;
        UseAccelRight = 0;
        UseBrakeRight = 1;
        motorLeftSpeedRpmSet = motorSpeedMaxRpm ;
        motorRightSpeedRpmSet = -motorSpeedMaxRpm;
        stateEndOdometryRight = odometryRight - (int)100 * (odometryTicksPerCm * PI * odometryWheelBaseCm / Tempovar);
        stateEndOdometryLeft = odometryLeft + (int)100 * (odometryTicksPerCm * PI * odometryWheelBaseCm / Tempovar);

      } else {
        UseAccelLeft = 0;
        UseBrakeLeft = 1;
        UseAccelRight = 1;
        UseBrakeRight = 0;
        motorLeftSpeedRpmSet = -motorSpeedMaxRpm ;
        motorRightSpeedRpmSet = motorSpeedMaxRpm;
        stateEndOdometryRight = odometryRight + (int)100 * (odometryTicksPerCm * PI * odometryWheelBaseCm / Tempovar);
        stateEndOdometryLeft = odometryLeft - (int)100 * (odometryTicksPerCm * PI * odometryWheelBaseCm / Tempovar);


      }
      OdoRampCompute();
      break;


Into each permanent state there is now.

A test to know if the OdoCible is reach.
A test to wait until a motor completly stop if rotation is inverted.
And the next state.


Here sample of code

Code:
case STATE_PERI_OUT_STOP:

      if ((odometryRight >= stateEndOdometryRight) && (odometryLeft >= stateEndOdometryLeft) )
        if (motorLeftPWMCurr == 0 && motorRightPWMCurr == 0) { //wait until the left motor completly stop because rotation is inverted
          setNextState(STATE_PERI_OUT_REV, rollDir);
        }
      motorControlOdo();

      break;

    case STATE_PERI_OUT_REV:
      if ((odometryRight <= stateEndOdometryRight) && (odometryLeft <= stateEndOdometryLeft) )
        if (rollDir == RIGHT) {
          if (motorLeftPWMCurr == 0 ) { //wait until the left motor completly stop because rotation is inverted

            setNextState(STATE_PERI_OUT_ROLL, RIGHT);
          }
        }
        else
        {
          if (motorRightPWMCurr == 0 ) { //wait until the left motor completly stop because rotation is inverted
            setNextState(STATE_PERI_OUT_ROLL, LEFT);
          }
        }
      motorControlOdo();
      break;

    case STATE_PERI_OUT_ROLL:

      if (mowPatternCurr == MOW_LANES_ODO) {
        
        if (rollDir == RIGHT) {
          if ((odometryRight <= stateEndOdometryRight) && (odometryLeft >= stateEndOdometryLeft))  setNextState(STATE_REACH_NEXT_LANE, 0);
        }
        else if (rollDir == LEFT) {
          if ((odometryRight >= stateEndOdometryRight) && (odometryLeft <= stateEndOdometryLeft))  setNextState(STATE_REACH_NEXT_LANE, 0);
        }

      }
      else  ///MOW_RANDOM, MOW_LANES, MOW_BIDIR,
      {
        if (rollDir == RIGHT) {
          if ((odometryRight <= stateEndOdometryRight) && (odometryLeft >= stateEndOdometryLeft) ) {
            if (motorRightPWMCurr == 0 ) { //wait until the left motor completly stop because rotation is inverted
              setNextState(STATE_PERI_OUT_FORW, RIGHT);
            }
          }
        }
        else {
          if ((odometryRight >= stateEndOdometryRight) && (odometryLeft <= stateEndOdometryLeft) ) {
            if (motorLeftPWMCurr == 0 ) { //wait until the left motor completly stop because rotation is inverted
              setNextState(STATE_PERI_OUT_FORW, LEFT);
            }

          }
        }
        motorControlOdo();
      }

      checkPerimeterBoundary();

      break;


And also new OdoRampCompute function to compute the accel brake etc..

Code

Code:
void Robot::OdoRampCompute() {
  //Compute the ramp to accel and brake the 2 wheels (very important for small distance)
  stateStartOdometryLeft = odometryLeft;
  stateStartOdometryRight = odometryRight;
  PwmRightSpeed = min(motorSpeedMaxPwm, max(-motorSpeedMaxPwm, map(motorRightSpeedRpmSet, -motorSpeedMaxRpm, motorSpeedMaxRpm, -motorSpeedMaxPwm, motorSpeedMaxPwm)));
  PwmLeftSpeed = min(motorSpeedMaxPwm, max(-motorSpeedMaxPwm, map(motorLeftSpeedRpmSet, -motorSpeedMaxRpm, motorSpeedMaxRpm, -motorSpeedMaxPwm, motorSpeedMaxPwm)));
  //try to find when we need to brake the wheel (depend of the distance)
  //compute the movingtime in millis()
  if (abs(stateStartOdometryLeft - stateEndOdometryLeft) >= odometryTicksPerRevolution)  OdoRampLeft = 0.8 * odometryTicksPerRevolution;
  else OdoRampLeft = abs(0.33 * odometryTicksPerRevolution);

  if (abs(stateStartOdometryRight - stateEndOdometryRight) >= odometryTicksPerRevolution)  OdoRampRight = 0.8 * odometryTicksPerRevolution;
  else OdoRampRight = abs(0.33 * odometryTicksPerRevolution);

  movingTimeLeft = (abs(stateStartOdometryLeft - stateEndOdometryLeft) * odometryTicksPerRevolution * motorSpeedMaxRpm / 60000); //0.58= nb ticks/ms at max speed
  movingTimeRight = (abs(stateStartOdometryRight - stateEndOdometryRight) * 0.58); //0.58= nb ticks/ms at max speed

  if (movingTimeLeft >= motorOdoAccel) accelDurationLeft = motorOdoAccel;
  else   accelDurationLeft =  movingTimeLeft / 2;

  if (movingTimeRight >= motorOdoAccel) accelDurationRight = motorOdoAccel;
  else   accelDurationRight =  movingTimeLeft / 2;

  Console.print(" ************************************ compute  at  ");
  Console.print(millis());
  Console.print(" movingTimeLeft ");
  Console.print(movingTimeLeft);
  Console.print(" movingTimeRight ");
  Console.print(movingTimeRight);
  Console.print(" accelDurationLeft ");
  Console.print(accelDurationLeft);
  Console.print(" accelDurationRight ");
  Console.print(accelDurationRight);
  Console.print(" OdoRampRight ");
  Console.print(OdoRampRight);
  Console.print(" OdoRampLeft ");
  Console.print(OdoRampLeft);
  Console.println(stateEndOdometryRight);


And the new motorControlOdo



Code:
void Robot::motorControlOdo() {

  // call to reach a ODO cible  accel and slow is used to smooth the movement of the mower
  //Stop motor independently when the cible is reach
  //
  if (UseBrakeLeft && (motorLeftSpeedRpmSet >= 0) && (stateEndOdometryLeft - odometryLeft <= -20)) {//Forward left need -20because when stop the ticks can move
    PwmLeftSpeed = 0;
    motorLeftSpeedRpmSet = 0;
    motorLeftRpmCurr = 0;
  }
  if (UseBrakeRight && (motorRightSpeedRpmSet >= 0) && (stateEndOdometryRight - odometryRight <= -20)) {//right
    PwmRightSpeed = 0;
    motorRightSpeedRpmSet = 0;
    motorRightRpmCurr = 0;
  }
  //Reverse
  if (UseBrakeRight && (motorRightSpeedRpmSet <= 0) && (stateEndOdometryRight - odometryRight >= 20)) {//right
    PwmRightSpeed = 0;
    motorRightSpeedRpmSet = 0;
    motorRightRpmCurr = 0;
  }
  if (UseBrakeLeft && (motorLeftSpeedRpmSet <= 0) && (stateEndOdometryLeft - odometryLeft >= 20)) {//left
    PwmLeftSpeed = 0;
    motorLeftSpeedRpmSet = 0;
    motorLeftRpmCurr = 0;
  }
  //if (millis() < (stateStartTime + 50))return;  // Possible to set a pause between movement
  if (millis() < nextTimeMotorOdoControl) return;
  nextTimeMotorOdoControl = millis() + 15;

  //LEFT WHEEL

  leftSpeed = PwmLeftSpeed ; //Set first to Normal speed and stay like this if not change  by accel or brake so limit the compute time
  if (motorLeftSpeedRpmSet > 0) { //forward left wheel
    if ((UseAccelLeft) && (millis() - stateStartTime < accelDurationLeft)) { //Accel mode for duration
      //Sinus accel
      //angleCorresp = map(millis() - stateStartTime, 0, accelDurationLeft, 0, 85);
      //leftSpeed = PwmLeftSpeed * sin(radians(angleCorresp)); //convert degree to radians
      //Linear accel
      angleCorresp = map(millis() - stateStartTime, 0, accelDurationLeft, 0, 100); // map from 0 to 100%
      leftSpeed = PwmLeftSpeed * angleCorresp / 100;

    }
    if (UseBrakeLeft && (odometryLeft > stateEndOdometryLeft - (OdoRampLeft))) { //Braking mode by odometry

      angleCorresp = map(abs(stateEndOdometryLeft - odometryLeft), OdoRampLeft, 0, 90, 15);
      leftSpeed = PwmLeftSpeed * sin(radians(angleCorresp));
    }
  }

  if (motorLeftSpeedRpmSet < 0) { //reverse left wheel
    if ((UseAccelLeft) && (millis() - stateStartTime < accelDurationLeft)) { //Accel mode for duration
      //Sinus accel
      // angleCorresp = map(millis() - stateStartTime, 0, accelDurationLeft, 0, 85);
      // leftSpeed = PwmLeftSpeed * sin(radians(angleCorresp)); //convert degree to radians
      //Linear accel
      angleCorresp = map(millis() - stateStartTime, 0, accelDurationLeft, 0, 100);
      leftSpeed = PwmLeftSpeed * angleCorresp / 100;
    }
    if (UseBrakeLeft && (odometryLeft < stateEndOdometryLeft + OdoRampLeft)) { //Braking mode by odometry
      angleCorresp = map(abs(stateEndOdometryLeft - odometryLeft), OdoRampLeft, 0, 90, 15);
      leftSpeed = PwmLeftSpeed * sin(radians(angleCorresp));
    }
  }

  //  RIGHT WHEEL
  rightSpeed = PwmRightSpeed ; //Normal speed
  if (motorRightSpeedRpmSet > 0) { //forward Right wheel
    // Console.print(" FR rotate ");
    if (UseAccelRight && (millis() - stateStartTime < accelDurationRight)) { //Accel mode for duration
      //Sinus accel
      // angleCorresp = map(millis() - stateStartTime, 0, accelDurationRight, 0, 85);
      //rightSpeed = PwmRightSpeed * sin(radians(angleCorresp));
      //Linear accel
      angleCorresp = map(millis() - stateStartTime, 0, accelDurationRight, 0, 100);
      rightSpeed = PwmRightSpeed * angleCorresp / 100;

    }
    if (UseBrakeRight && (odometryRight > stateEndOdometryRight - OdoRampRight)) { //Braking mode by odometry
      angleCorresp = map(abs(stateEndOdometryRight - odometryRight), OdoRampRight, 0, 90, 15);
      rightSpeed = PwmRightSpeed * sin(radians(angleCorresp));
    }
  }
  if (motorRightSpeedRpmSet < 0) { //reverse Right wheel
    if (UseAccelRight && (millis() - stateStartTime < accelDurationRight)) { //Accel mode for duration
      //Sinus accel
      //angleCorresp = map(millis() - stateStartTime, 0, accelDurationRight, 0, 85);
      //rightSpeed = PwmRightSpeed * sin(radians(angleCorresp));
      //Linear accel
      angleCorresp = map(millis() - stateStartTime, 0, accelDurationRight, 0, 100);
      rightSpeed = PwmRightSpeed * angleCorresp / 100;
    }
    if (UseBrakeRight && (odometryRight < stateEndOdometryRight +  OdoRampRight)) { //Braking mode by odometry
      angleCorresp = map(abs(stateEndOdometryRight - odometryRight), OdoRampRight, 0, 90, 15);
      rightSpeed = PwmRightSpeed * sin(radians(angleCorresp));
    }
  }

  Console.print(millis());
  Console.print(" *** Lspeed= ");
  Console.print(leftSpeed);
  Console.print(" ODO ");
  Console.print(stateStartOdometryLeft);
  Console.print("/");
  Console.print(odometryLeft);
  Console.print("/");
  Console.print(stateEndOdometryLeft);
  Console.print(" ************************************ Rspeed= ");
  Console.print(rightSpeed);
  Console.print(" ODO ");
  Console.print(stateStartOdometryRight);
  Console.print("/");
  Console.print(odometryRight);
  Console.print("/");
  Console.println(stateEndOdometryRight);

  setMotorPWM(leftSpeed, rightSpeed, false );
}


For the moment it's work but need more test to be sure there is no problem on slope and motordriver because the mowing is very fast.
The main problem is for small movement (less the 1/4 of Wheel turn) , If PWM too low the motor don't turn and the Odocible never reach.
Maybe i add a Maxtime into each state and jump to the other if trigger ?

Video .
https://youtu.be/5usduXYCLEg
 
@Max
If you have download my firmware
The #Define for the mini is not in use and you need your parameters in PFOD for your platform.


First in setting mow force mowing to YES fo Safety.

Do not use the IMU for the beginning.

IN setting odometry 3 very impotant value
Ticks per one rev (?put your value for your platform?)
Ticks per Cm (?put your value for your platform?)
Wheel base cm (?put your value for your platform?)


In setting motor
Speed max Rpm (?put your value for your platform?)
Speed max pwm (?put your value for your platform?)
Speed odo mini (?put your value for your platform?)
Speed odo maxi (?put your value for your platform?)


After that you can use the test odo/imu
The one and five turns wheel nedd to be perfect
The rotate 180 deg also.

IF this is ok you can now setup the imu
 
I am going to see that the arduremote don't show the ODO test it was only working in PFOD.
So i separe the 2 TEST in the Menu and send again the new ZIP.

The new Menu
MAINMENU.jpg


First the Motor setting
Set the PWM value so that the motor run at a normal speed and the RPM also
The speed ODO Minimum is used like that :
when the motor brake it need to have enought power to reach the odometry cible so do not put a too small value.
If the value is big the mower don't brake enought and the movement is not smooth.
The speed ODO Maximum is used when the Wheel need small distance.

SETTINGMOTOR.jpg



Now the odometry
go in setting odometry and set the value for your platform
SETTINGODO.jpg


After that use the Test ODO to verify your setting
TESTODOMENU.jpg


When the ODO is good https://youtu.be/gsvxrxaf5k4 https://youtu.be/t-LnT02_u3o


you can add the IMU and made the calibration(No change between the 1.07 Azurit so normaly no problem)
Use the Test IMU to verify.
You can view that the North and South are not directly opposite and it's the main problem for By lane Mowing. https://youtu.be/cPeBiLXgWa0 After in the ODO test you can set the mower to Rotate non stop
and go in PLOT
and IMU
for a perfect result look at the WIKI alexander show a beautiful curve.
To stop the movement use the COMMAND and OFF.


https://youtu.be/b3Z5cL9b2mc Attachment: https://forum.ardumower.de/data/media/kunena/attachments/3545/MAINMENU.jpg/
 
Zuletzt bearbeitet von einem Moderator:
Dear Bernard
I will keep to this post for your software.
I have always had problems with my quadrature encoders. Before I buy some alternative encoders I want to see if I can understand and fix these.
When I was using the mega the only way I could get my 400 ticks per rev encoders working predictably was to use a value of 1374 ticks per rev. The is very strange. I have noticed that all encoders of this type have output channels a and b. Also the odometer input socket on the pcb 1.3 board only has 3 inputs. These are plus minus and 1 signal. Currently I have only one channel connected the other is left floating.
Question am I supposed to connect a and b channels together to make a blended channel to be the input or is using only one channel he correct approach
regards
Max
 
Hi.
The PCB 1.3 manage only one signal so the a and b can't be use at the same time .
But normaly with only one no problem but half of ticks and you can't know the direction of the rotation.
What is exactly your encodeur ?
 
Hi.
To be confirm but I understand your problem the motor is 200RPM at the Output of the gearbox

The 1:30 indicate that the motor itself run at 6000RPM.
If you put the encoder like that it's normal it can't work because the arduino need to manage 6000*100 ticks/minute and it's too important.

To solve this You need to try to mask and open the encoder disk so that a reading is possible.

If the 200RPM is OK for your platform you have 100*30=3000 ticks per rev
The blank on the disk are very small so the impuls is so fast than the DUE can't manage it.

You need to mask 1/3 of the disk to have only 30Ticks/rev(on the disk) and when i say mask it's also enlarge the blank to increase the impuls duration.

Good luck.
 
Other solution:
If you have the PCB1.3
Maybe it's possible to use the ODO diviser but not sure the Impuls duration is OK.
(need solder on the Odoteiler different than the normal) :
 
A lot of DUE clones doesn't start the code when power on external. Therfore the board will be reseted on startup. This should not be a problem with original Arduino DUE.
 
Bernard
On your mower do you use one to two perimeter fence detectors. Also any other changes from normal. It has been raining here so not much progress. I will start again once the weather clears up
regards
Max
 
Hi Max
I use only the left coil like the original AZURIT.
My last code can read the 2 coils but without any advantage so why to manage the right coil for Nothing.
Main change between the original are that all the rotation and reverse use Odometry for accurate and smooth movement.
Also The ByLane mowing use a special parameter to generate path.
it's difficult to explain but here a brief description.
0 is North
90 is East
-180 or 180 are South
-90 is west

In PFOD 3 lanes are defines they have to be adjust depending of the orientation of your lawn.
Lane1 45Deg
Lane2 90Deg
Lane3 135Deg

For each 3 lanes 2 other direction are defines
One if the mower rotate to the Left and the other if rotate to the Right.
So for example
Opposites right1 = -170
Opposites left1 = -120

Opposites right2 = -91
Opposites left2 = - 89

Opposites right3 = -30
Opposites left3 = -60

So by this way the mower make perfect parallel lane in Lane2 and go fast from one to the other part of the area with lane1 or lane3 because the negative and positive direction are not parallel.

For the moment the Random mode work perfectly but i need more test for the ByLane to be sure all the area is mow with best result than random mode and also for the accurate of the IMU.
By.
 
Hi Bernard, How are you. I am now back on with my project.

When you say wheel base do you mean the distance from the front wheels to back wheel or between the front wheels

Tick per cm is calculated by dividing the ticks (324 in my case) by the circumference of the wheel Y/N

With your software does the sonar work as I put sonar on and I am not getting any values

I would be very grateful if you could publish your important parameters. Also can you supply some explaination for what the PID is doing

When it is used for the different settings (motor, perimeter, imu etc.

Some examples would be really great like Increasing P in IMU makes the correction happen faster. Reducing the P make the turning slower. Increasing I make the correction ? Reducing I makes the correction ? And increasing D makes the correction etc etc

I will appreciate what ever you can provide. Sorry if I ask for too much

Max
 
Hi max
When you say wheel base do you mean the distance from the front wheels to back wheel or between the front wheels
.
It's the distance between the 2 motor wheels in CM
This value is used to compute how many ticks you need to rotate for 45 deg for example.

Tick per cm is calculated by dividing the ticks (324 in my case) by the circumference of the wheel Y/N
Yes value is easy to verify with test motor 1 turn.

With your software does the sonar work as I put sonar on and I am not getting any values
Sonar, I don't use for the moment but i simply add // everywhere on the Azurit so try to remove them

I try to make other video with good and wrong parameter so you can see the difference between
BY
 
Hi Bernard

I am currently using Actobotic 313 rpm motors. However I want to get the mini to be as close to a ardumower as possible so when I make changes to the mini they will translate to the larger ardumower. I reason I do this as it is easier for me to work in the winter in doors with a mini and then to use the ardumowe out side on dry days. i.e. hopefully this will be more productive.
To achieve this I have just bought the Actobotics 32rpm motors as they have the same rpm as my Ardumower motors. https://www.servocity.com/32-rpm-hd-premium-planetary-gear-motor-w-encoder These motor, the encoders have 3168 cycles per revolution. whereas my current ones have 324.
You kindly provided some guidance regarding the ODO divider on the underside of the pcb. So I know which connection to bridge what is the most tick the DUE can handle. i.e. what number do I need to reduce the 3168 down to.
Lastly how do I know which is connection 1,2,3 etc as they are not marked

Thank you for clearing up the wheel base because in English the wheel base is between the front and back wheels. In this case it is completely different

regards

Max
 
Hi Bernard

Regarding the calibration of the IMU. I think the calibration results are stored on the eprom of the RTC Y/N?
My results are not being saved so have ordered another RTC (second bad one)

I followed the wiki instruction for the two IMU calibration carefully. I got these values do they look correct to you?

accofs 2.12 , -2.07, -14.8
accscale 521.87,522.45,499.49
comof 64 ,-93, 98
com scale 1225, 1306 ,1066

can you give me an idea of what they mean so I can check them against the position of the unit in the chassis

Assuming the 2 screw holes of the IMU are pointing north and the components on the board are facing up
If I lift the left edge up the roll is positive and the left edge down the roll is negative Y/N?
If I move the 2 screw holes down i.e. pitch down the pitch becomes more positive and if I move them up becomes more negative (this seems the wrong sense)
the yaw does follow a 0 to +180 and 0 to -180. however the zone to the sw seems more compressed in values than rest of the rotation.
Best Regards

Max
 
I am also setting up my full scale Ardumower. I have found by testing (1 and 5 wheel rotations) that the ticks per rev for the Ardumower shop motors is 1050. Does this seem correct
regards

Max
 
Oben