Differential drive vehicle with RTK autosteer

Hello all,

An idea I want to work on this winter is to build a 4-wheel cart that can drive 1 to 2 persons through a crop automatically. For hand-weeding in crop lines but it could be multi-purpose, even fully autonomous. Its features would be:

-differential drive, so 2 independent electric motor drives on 2 propulsion wheels. 2 caster wheels just follow
-manual mode by joystick for maneuvering and automatic mode by RTK-GPS and AgOpenGPS
-lightweight, simple, on a 1 or 2 meter trackwidth so a single person can bring it to the field as a bicycle cart and get going quickly

Some key hardware I was thinking to gather:

-mostly aluminium framework with extrusion profiles that can be bolted together or glued
-2x 250W 24V brushed motors at ± 300 rpm
-2x 20" BMX wheels for propulsion, chain drive
-2x Pololu SMC G2 motor controllers - together they can take care of the manual joystick mode. However no feedback of speed (alternative is JRK, more expensive).
-2x rotary encoder on the chain drive for wheel speed feedback
-24V 20Ah lithium battery or similar + solar panel on top if necessary for 1-day autonomy
-one receiver/rover F9P with a windows tablet with AOG

A challenge will be to modify the arduino code to work with AgOpenGPS and control a differential drive vehicle, however it should be easier than a steered vehicle (or so I’d think). There’s a way to convert difference in speed of the propulsion wheels to a steering angle so that can be sent back to AgOpenGPS. I hope to succeed in this and eventually get it on github, so any tips or resources are welcome.

Some inspiration was already on the combine forum:
-lightweight autonomous bot build lightweight autonomous bot build | The Combine Forum
-this amazingly cool video of an autonomous mower, I think this is a reference of Andreas Autonomous Mowing In Amstetten Austria with AgOpenGPS - YouTube

Search for ‘chariot binage electrique’ for an idea of such vehicles. In France these are already common in the field although RTK and autosteer integration is yet rare.

3 Likes

Motors and encoders arrived so I was able to make a prototype and write some code.

Turns out the Pololu SMC is not able to switch between seral/I2C and analog input without the pc software, so I had to find another way for the manual maneuver mode. Luckily it was easy to implement in arduino thanks to these sources:
https://www.impulseadventure.com/elec/robot-differential-steering.html

Then the code for calculation of steering angle. It is a theoretical steering angle based on the differential speed of the wheels and the vehicle dimensions, some formulas to be found in this source:

The reading of encoder pulse changes since last measurement, dPos:

  currentPosL = rotL.read();          //get encoder position, derive pulse speed
  currentPosR = -rotR.read();         //inverse mounting
  if (currentPosL != lastPosL || currentPosR != lastPosR) {
    dPosL = currentPosL - lastPosL;
    lastPosL = currentPosL;
    dPosR = currentPosR - lastPosR;
    lastPosR = currentPosR;
    }
    else {
    dPosL=0;
    dPosR=0;
    }

Calculation of steer angle based on dPos:

 if ((abs(dPosL) < dPosTreshold && abs(dPosR) < dPosTreshold) || (dPosL == dPosR))
      { 
      steerAngleCurrent = 0;
      steerAngleActual = 0;
     }
    else if (dPosL < dPosR) 
      { 
      R_turnradius = (trackwidth*0.5)*(dPosL+dPosR)/(dPosR-dPosL);  
      steerAngleCurrent = -180/PI*atan2(wheelbase, R_turnradius);   //result of atan2 is radians, *180/pi gives degrees
     }
    else if (dPosL > dPosR)
      { 
       R_turnradius = (trackwidth*0.5)*(dPosL+dPosR)/(dPosL-dPosR);        
       steerAngleCurrent = 180/PI*atan2(wheelbase, R_turnradius);  
     }

 steerAngleActual += steerangle_smoother*(steerAngleCurrent-steerAngleActual); // simple low pass filter
   
     if (watchdogTimer < 10)// && modeSwitch == 1)                   //autosteer driving
      { 
        steerAngleError = steerAngleActual - steerAngleSetPoint;   //calculate the steering error
speedcontrolPID();     //do the motor speed pid
calcSteeringPID();  //do the steer angle pid
motorDrive();       //out to motors the L and R motorspeed
  }     
else          // set to manual if communication is lost or manual mode is selected
  {   //we've lost the comm to AgOpenGPS, or just stop request
    manualmode();
  }

This is the code for 1) a PID to adjust throttle of left and right wheel to achieve the desired speed, and 2) the PID to adjust differential L/R throttle to achieve desired steer angle. Finally 3) the main throttle of the vehicle is adjusted and throttle L/R is sent to the controllers.
I am new to coding and especially PID so probably some things to improve here.

 void speedcontrolPID(void)      //first adjust the individual throttle of left and right wheels so that their speed is corrected
    {                                             
    lastthrottle = (throttleL + throttleR)/2;                       //throttle difference to pick up later = new throttle - last throttle
    
    speedL = dPosL * dPosUnit;                                                        //throttle setpoint, speed feedback. Thanks to http://andrewjkramer.net/pid-motor-control/
    adjustL = speedKp * (throttleL - speedL) + speedKd * (speedL - lastspeedL);       //error is throttle - speed
    lastspeedL = speedL;
    
    speedR = dPosR * dPosUnit;
    adjustR = speedKp * (throttleR - speedR) + speedKd * (speedR - lastspeedR); 
    lastspeedR = speedR;

    throttleL -= adjustL;
    throttleR -= adjustR;
  } 
  
//######################################################################################### 

void calcSteeringPID(void)       //second adjust the differential -p/+p throttle so that steer angle is corrected
 {
  
    //Proportional only
    pValue = steerSettings.Kp * steerAngleError *steerSettings.Ko;          
    pDrive = (constrain(pValue, -maxmotorspeed*maxdiffdrive, maxmotorspeed*maxdiffdrive));  //pwmDrive originally was -255,255 so a factor 10 next to Kp, Ko is included. Maxdiffdrive limit for safety
  
    //add min throttle factor so no delay from motor resistance.
    if (pDrive < 0 ) pDrive -= (steerSettings.minPWMValue);
    else if (pDrive > 0 ) pDrive += (steerSettings.minPWMValue);
    
    if (pDrive > maxmotorspeed*maxdiffdrive) pDrive = maxmotorspeed*maxdiffdrive;
    if (pDrive < -maxmotorspeed*maxdiffdrive) pDrive = -maxmotorspeed*maxdiffdrive;
  
    throttleL += pDrive;
    throttleR -= pDrive;

 }

//#########################################################################################


  void motorDrive(void)           //third adjust main throttle and out to the motors
  {   
   pDisplay = pDrive; 
    
   throttle = map(analogRead(throttle_readpin), 0, 1023, 0, maxmotorspeed);
//    throttle = 1600;
   pedalbrake = map(analogRead(brake_readpin), 200, 800, 1, 0);
   if (pedalbrake < 0) pedalbrake = 0;
   if (pedalbrake > 1) pedalbrake = 1;
   throttle *= pedalbrake;

   throttleL += (throttle - lastthrottle);     
   throttleR += (throttle - lastthrottle);

   if (throttleL > maxmotorspeed) throttleL = maxmotorspeed;
   if (throttleL < -maxmotorspeed) throttleL = -maxmotorspeed;
   if (throttleR > maxmotorspeed) throttleR = maxmotorspeed;
   if (throttleR < -maxmotorspeed) throttleR = -maxmotorspeed;
   
   setMotorSpeed(throttleL, I2C_L);  //set left motor speed
   setMotorSpeed(throttleR, I2C_R);  //set right motor speed
  }

Here is a video showing first a demo of the manual mode and then some fiddling with the drive function in Agopengps. It’s a proof of concept but as you can see a lot to improve. Calculated steer angle moves wildly, maybe partly due to mechanics like a loose chain link, probably more due to insufficient code.

First steps are taken, feel free to comment! Next I will probably build a small version of the vehicle that can carry the battery, to check if it can hold a line in the real world.

1 Like

You will definitely need a dual antenna guidance system and be able to detect direction as well.

2 Likes

After changing some signs here and there in the code, this is how the prototype drives at 0.2 - 1 km/h which is somewhat the desired speed range. It can hold the line well at these speeds, in pure pursuit the lookahead is 0.5m for good reaction.
Only problem for now, is that the setup could use some more gearing for the lower speeds. The movement gets bumpy as you can see in the video a little bit. Also at very low speed, the heading fluctuates in AgOpenGPS. A goal is to keep it low-budget and I want to avoid a dual GPS setup, even an IMU-sensor as they are not really reliable.

3 Likes

That is awesome!!!

Edit: I just read the CombineForum thread and saw it is the same project I saw posted on fast.ai.

Nice progress. There are so many applications of low power (hence safe-ish) autonomous bots for smaller farming operations. I have quite a few ideas and will be watching this project with interest. Have you seen http://www.opensprayer.com/ ? Will be deep learning for weed detection (I saw the guy posting on fast.ai forums) but no updates recently.