Introduction: ActoBitty Line Follower (ABLF)

Line following robot I built using Actobotics ActoBitty as the base (ABLF = ActoBitty Line Follower).

Materials from Actobotics (part # in parentheses):

    Materials from Other Vendors:

    Mounting plate for Romeo:

    Misc. Supplies/Tools:

    Step 1: Build ActoBitty Base

    The directions from Actobotics are easy to follow, so I won't repeat them:

    • Video:

    Only difference is that I used 3" wheels hoping it would be faster. I did not use the battery, Arduino, or caster mounts that came with the kit.

    Step 2: Add Hub Mount to Front

    Quadmount Hub C allows flush mounting plate up front the the sensor mount will later be attached to.

    Step 3: Install Mounting Plate for Board

    Front screw attaches underneath to a 1.25" offset instead of a nut. This does not add anything structurally, but the offset provides a stop for the battery, to keep it from sliding any further forward.

    Step 4: Install Caster and Battery Mount

    Attach the 5/8" roller transfer to the bottom of the dual mounting plate. Add the dual side mounts to the other side. Note that one of the screws attaching the roller transfer goes through to one end of the mount on the other side. Attach the assembly into the channel.

    I then added a long socket screw into the round offset described in the previous step that is hanging down into the channel. This is just to give it a little stability. That's why there is a single socket head in the "middle" of the assembly.

    Use the other 1.25" standoff to mount across the channel. The socket heads for the motors provide the rearmost support for the battery, This standoff then provides the front support, and the vertical standoff keeps the battery from sliding forward. Side mount is put across bottom so that L-shaped beam bracket can be attached to keep battery in at the back.

    Step 5: Mount Sensor

    The two center holes on the SparkFun Sensor has the same spacing as the mini-channel. Makes it a little asymmetric, but that isn't a problem. The mini-channel then mounts to the square beam bracket, when then mounts to the hub mount. The north, east, and west screw holes around the central hole in the hub mount are threaded, so no nuts are needed behind. Choice of 0.25" spacers to lower the sensor was trial and error. It gives decent clearance, but keeps the sensors pretty close to the surface. Could easily use shorter spacers for more clearance.

    Step 6: Install Microcontroller and Connectors

    Use M3 screws to mount Romeo V2 board to mounting plate. Wires are installed as shown. I just bent some long pin connectors to make it easier to connect the motors. These were screwed into board motor connectors. JST connector was shortened and screwed into board power connector.

    I have the materials to make a specific wiring harness, but you can easily use individual female jumpers - just not quite as convenient to plug/unplug. If you get the kind of F-F jumpers as I indicated, they come all connected together, so you could peel off 4 together.

    Wiring for I2C connection shown (your colors may vary):

    • Red = 5V
    • Black = Ground
    • White = SCL
    • Green = SDA

    Step 7: Install Software and Libraries

    If you don't have the Arduino "Integrated Development Environment" (IDE) installed, you'll need that. Go to https://www.arduino.cc/en/Guide/HomePage and choose what kind of computer you have in the "Install Arduino Software" section of the page.

    The SparkFun library needs to be installed into your Arduino IDE. This is now easy to do. In Arduino go to "Sketch" > "Include Library" > "Manage Libraries...". This will open the library manager. In the "filter results" box type "Sparkfun line". The top library result should now be the SparkFun Line Follower Array. Click the blue "more info" link, then click the "Install" button. The most recent version of the library should be loaded by default. See https://learn.sparkfun.com/tutorials/installing-an... for more details if you need them.

    The DFRobot Romeo V2 is an Arduino Leonardo compatible board, so choose "Arduino Leonardo" as the board under "Tools" > "Board" in the Arduino IDE.

    Step 8: Basic Program

    This program was modified from the SparkFun Most Basic Line Follower. It works, but doesn't do sharp turns well. I'm still working on proper PID line sensing.

    /* ABLB
     * ActoBitty Line Bot 
     *
     * base robot Actobotics ActoBitty:
     * https://www.servocity.com/html/actobitty_2_wheel_robot_kit.html#.VthCuvkrI-U
     * 
     * microcontroller board DFRobot Romeo 2 (has built in motor controller):
     * http://www.dfrobot.com/wiki/index.php/Romeo_V2-All_in_one_Controller_(R3)_(SKU:DFR0225)
     *
     * mount panel for board:
     * http://www.thingiverse.com/thing:1377159
     * 
     * line follower array from Sparkfun:
     * https://github.com/sparkfun/Line_Follower_Array
     *  
     */
    
    
    #include "Wire.h"              // for I2C
    #include "sensorbar.h"         // needs SparkFun library
    
    // Uncomment one of the four lines to match your SX1509's address
    //  pin selects. SX1509 breakout defaults to [0:0] (0x3E).
    const uint8_t SX1509_ADDRESS = 0x3E;  // SX1509 I2C address (00)
    //const byte SX1509_ADDRESS = 0x3F;  // SX1509 I2C address (01)
    //const byte SX1509_ADDRESS = 0x70;  // SX1509 I2C address (10)
    //const byte SX1509_ADDRESS = 0x71;  // SX1509 I2C address (11)
    
    SensorBar mySensorBar(SX1509_ADDRESS);
    
    // based on SparkFun MostBasicFollower code
    // Define the states that the decision making machines uses:
    #define IDLE_STATE 0
    #define READ_LINE 1
    #define GO_FORWARD 2
    #define GO_LEFT 3
    #define GO_RIGHT 4
    
    uint8_t state = 0;
    
    // Romeo standard pins
    int Lmotor = 5;                // M1 Speed Control
    int Rmotor = 6;                // M2 Speed Control
    int Ldir = 4;                  // M1 Direction Control
    int Rdir = 7;                  // M1 Direction Control
     
    
    void setup() {
     //Default: the IR will only be turned on during reads.
      mySensorBar.setBarStrobe();
      //Other option: Command to run all the time
      //mySensorBar.clearBarStrobe();
    
      //Default: dark on light
      mySensorBar.clearInvertBits();
      //Other option: light line on dark
      //mySensorBar.setInvertBits();
      
      //Don't forget to call .begin() to get the bar ready.  This configures HW.
      uint8_t returnStatus = mySensorBar.begin();
     /*
      if(returnStatus)
      {
    	  Serial.println("sx1509 IC communication OK");
      }
      else
      {
    	  Serial.println("sx1509 IC communication FAILED!");
      }
      Serial.println();
      */
      
    } // end setup()
    
    void loop() {
      uint8_t nextState = state;
      switch (state) {
      case IDLE_STATE:
        halt();       // Stops both motors
        nextState = READ_LINE;
        break;
      case READ_LINE:
        if( mySensorBar.getDensity() == 0)
        {
          nextState = IDLE_STATE;
          break;
        }
        else if( mySensorBar.getDensity() < 7 )
        {
          nextState = GO_FORWARD;
          if( mySensorBar.getPosition() < -50 )
          {
            nextState = GO_LEFT;
          }
          if( mySensorBar.getPosition() > 50 )
          {
            nextState = GO_RIGHT;
          }
        }
        else
        {
          nextState = IDLE_STATE;
        }
        break;
      case GO_FORWARD:
        fwd(128,128);
        nextState = READ_LINE;
        break;
      case GO_LEFT:
        fwd(16,160); // L a little slower R a bit faster
        nextState = READ_LINE;
        break;
      case GO_RIGHT:
        fwd(160, 16);
        nextState = READ_LINE;
        break;
      default:
        halt();       // Stops both motors
        break;
      }
      state = nextState;
      //delay(100);
    
    
    } // end loop()
    
    void halt(void)               // Stop
    {
      digitalWrite(Lmotor,LOW);   
      digitalWrite(Rmotor,LOW);      
    }   
    void fwd(byte a,byte b)       // Move forward
    {
      analogWrite (Lmotor,a);     // PWM Speed Control
      digitalWrite(Ldir,LOW);     // LOW for fwd
      analogWrite (Rmotor,b);    
      digitalWrite(Rdir,LOW);
    } 
    

    Step 9: PD Line Follower

    Slightly more sophisticated program. Still a work in progress, but this works. Kp = 1, Kd = 2

    /* ABLB
     * ActoBitty Line Bot 
     *
     * base robot Actobotics ActoBitty:
     *  https://www.servocity.com/html/actobitty_2_wheel_...
     * 
     * microcontroller board DFRobot Romeo 2 (has built in motor controller):
     *  https://www.servocity.com/html/actobitty_2_wheel_... 
     *
     * mount panel for board:
     *  https://www.servocity.com/html/actobitty_2_wheel_...
     * 
     * line follower array from Sparkfun:
     *  https://www.servocity.com/html/actobitty_2_wheel_...
     *  https://www.servocity.com/html/actobitty_2_wheel_...
     * position provided ranges from -127 (far L) to 127 (far R)
     * 
     * PID quick tutorial
     *  https://www.servocity.com/html/actobitty_2_wheel_...
     *  
     */
    
    
    #include "Wire.h"              // for I2C
    #include "sensorbar.h"         // needs SparkFun library
    
    // Uncomment one of the four lines to match your SX1509's address
    //  pin selects. SX1509 breakout defaults to [0:0] (0x3E).
    const uint8_t SX1509_ADDRESS = 0x3E;  // SX1509 I2C address (00)
    //const byte SX1509_ADDRESS = 0x3F;  // SX1509 I2C address (01)
    //const byte SX1509_ADDRESS = 0x70;  // SX1509 I2C address (10)
    //const byte SX1509_ADDRESS = 0x71;  // SX1509 I2C address (11)
    
    SensorBar mySensorBar(SX1509_ADDRESS);
    
    // will try to avoid floating point math
    const byte Kp = 1;
    const byte Kd = 2;
    
    const byte MAXSPEED = 128; // slow things down for testing purposes
    int Lspeed = MAXSPEED;     // int since may exceed 255 in calculations, but will ultimately be constrained
    int Rspeed = MAXSPEED;
    
    
    
    const int ButtonPin = 0;
    int buttonVal = 0;
    boolean goFlag = false;
    int error = 0;
    int lastError = 0;
    
    
    // Romeo standard pins
    int Lmotor = 5;                // M1 Speed Control
    int Rmotor = 6;                // M2 Speed Control
    int Ldir = 4;                  // M1 Direction Control
    int Rdir = 7;                  // M1 Direction Control
     
    
    void setup() {
     //Default: the IR will only be turned on during reads.
      mySensorBar.setBarStrobe();
      //Other option: Command to run all the time
      //mySensorBar.clearBarStrobe();
    
      //Default: dark on light
      mySensorBar.clearInvertBits();
      //Other option: light line on dark
      //mySensorBar.setInvertBits();
      
      //Don't forget to call .begin() to get the bar ready.  This configures HW.
      uint8_t returnStatus = mySensorBar.begin();
     /*
      if(returnStatus)
      {
    	  Serial.println("sx1509 IC communication OK");
      }
      else
      {
    	  Serial.println("sx1509 IC communication FAILED!");
      }
      Serial.println();
      */
      
    } // end setup()
    
    void loop() {
    
      buttonVal = analogRead(ButtonPin);
      
      if (buttonVal < 30){       // button 1
        halt();
        goFlag = false;
      }
      else if (buttonVal < 175){ // button 2 
        //Command to run all the time - allow calibration
        mySensorBar.clearBarStrobe();
        int i = mySensorBar.getPosition();
      }
      else if (buttonVal < 360){  // button 3 
        //Default: the IR will only be turned on during reads.
        mySensorBar.setBarStrobe();
      }
      
    //  else if (buttonVal < 540){  // button 4
    //    // for future use
    //  }
    
      else if (buttonVal < 800){  // button 5 - run line follower program
        goFlag = true;
        delay(3000); //  3 sec delay to back off
        // include visual indicator later
      }
      
      if (goFlag) {
        error = mySensorBar.getPosition(); // position gives distance from midline, i.e. the error
    
        // want to be as fast as possible, so will just slow down necessary wheel for correction
        // rather than trying to speed up one and slow the other
        if (error < 0){           // robot has drifted right; slow down L wheel
          Rspeed = MAXSPEED;
          Lspeed = MAXSPEED + (Kp * error) + (Kd * (error - lastError)); // plus since error is negative, will result in negative values for proportionate term
          Lspeed = constrain(Lspeed, 0, MAXSPEED);
          
        }
        else if (error > 0){      // robot has drifted L; slow down R wheel         
          Rspeed = MAXSPEED - (Kp * error) - (Kd * (error - lastError)); 
          Rspeed = constrain(Rspeed, 0, MAXSPEED);
          Lspeed = MAXSPEED;
        }
        else{                   // position is zero; full on both
          Rspeed = MAXSPEED;
          Lspeed = MAXSPEED;
        }
    
        fwd(Lspeed,Rspeed);
    
      } // end if (goFlag)
    } // end loop()
    
    
    void halt(void)               // Stop
    {
      digitalWrite(Lmotor,LOW);   
      digitalWrite(Rmotor,LOW);      
    }   
    
    
    void fwd(byte l,byte r)       // Move forward
    {
      analogWrite (Lmotor,l);     // PWM Speed Control
      digitalWrite(Ldir,LOW);     // LOW for fwd
      analogWrite (Rmotor,r);    
      digitalWrite(Rdir,LOW);
    }  
    
    // don't need these functions for basic line follower
    //void rev(byte l,byte r)       // Reverse
    //{
    //  analogWrite (Lmotor,l);
    //  digitalWrite(Ldir,HIGH);   
    //  analogWrite (Rmotor,r);    
    //  digitalWrite(Rdir,HIGH);
    //}  
    //void spinR(byte l, byte r)
    //{
    //  analogWrite (Lmotor,l);
    //  digitalWrite(Ldir,LOW);    // L fwd, R rev to spin R (clockwise)
    //  analogWrite (Rmotor,r);    
    //  digitalWrite(Rdir,HIGH);
    //}  
    //void spinL(byte l, byte r)
    //{
    //  analogWrite (Lmotor,l);
    //  digitalWrite(Ldir,HIGH);    // R fwd, L rev to spin L (counterclockwise)
    //  analogWrite (Rmotor,r);    
    //  digitalWrite(Rdir,LOW);
    //}  
    

    Step 10: Step 10: Software Archive

    I've put my various version on GitHub. Still definitely a work in progress.

    https://github.com/KFW/ABLB