Twitter icon
Facebook icon
LinkedIn icon
Google icon
Reddit icon
StumbleUpon icon
Del.icio.us icon

The Dandelion Hunter

Added to IoTplaybook or last updated on: 07/29/2019
The Dandelion Hunter

Story

Purpose

Dandelions are everywhere, and because they grow so quickly and spread seeds all over the place, they make for some of the most annoying weeds. It only takes a few days for a dandelion flower to appear after mowing, well before the grass has had enough time to catch up.

That’s why I wanted to build a robot that could hunt down flowering dandelions and end them before they have had enough time to grow to maturity.

hings used in this project

Hardware components

 
DFRobot Devastator Tank Chassis
 
× 1

 

Teensy 3.5
Teensy 3.5
 
× 1

Buy from Kickstarter

Buy from Adafruit

 

 
Pixy2 CV Camera
 
× 1  
 
N-Channel MOSFET
 
× 1  
 
DC High RPM Hobby Motor
 
× 1  
 
11.1v 1800mAh Battery
 
× 1  
Dual H-Bridge motor drivers L293D
Texas Instruments Dual H-Bridge motor drivers L293D
 
× 1

Buy from

Software apps and online services

Fusion 360
Autodesk Fusion 360
 
 

Buy from Newark

Buy from Adafruit

Buy from CPC

Buy from www.ti.com

Arduino IDE
Arduino IDE
 
  Buy
 
PixyMon
 
  Buy

Hand tools and fabrication machines

Soldering iron (generic)
Soldering iron (generic)
 
   
Multitool, Screwdriver
Multitool, Screwdriver
 
 

Buy

3D Printer (generic)
3D Printer (generic)
 
   

 

YouTube video - Dandelion Hunting Robot

 

The Chassis

Due to this being an outdoor robot, I wanted a chassis that had tracks and powerful motors to overcome tufts of grass and clumps of dirt. DFRobot’s Devastator Tank Chassis was a perfect fit for this project thanks to its tough tracks and aluminum body. Running the motors at 12v provides enough force to defeat low obstacles.

Computer Vision

I wanted this robot to be able to seek out flowered dandelions, which meant that it needed a way to see and process available targets. Rather than use a complex system that relied on object recognition, I went with the Pixy 2 camera, which relies on detecting objects with similar colors.

To get it to recognize dandelion flowers, I set it outside and loaded up PixyMon, Pixy’s PC to Pixy2 control software. I simply held up a flower to it and then drew a box around the yellow portion. Now the Pixy2 was able to detect its targets.

The Program

The premise of the program is simple: detect a flower, go up to it, and then cut it down. Thankfully, Pixy has created a library that allows for detected objects to be easily identified and tracked. The program causes the servo to slowly rotate and find possible targets. When one is found, its position is saved and then the tank rotates to align itself. Next, it moves forward while making small adjustments to keep the flower in front.

Objects seem to get larger the closer they get, so by measuring the size of the object’s bounding box, a basic size can be obtained. Once the flower is close enough, the tank backs up, turns on the cutting tool, and then drives forward to cut down the flower.

Hunting Down Dandelions

After waiting a few days, there were finally enough dandelions to cut down. I simply set it down and calibrated the Pixy to ensure it was detecting everything correctly. Then I added some power and let it go. Overall, it worked really well at cutting down those obnoxious weeds.

Future Improvements

Currently, there is no way to remotely or automatically stop the robot once it is finished, so I would like to implement a method that can shut it off once it can’t detect any more targets. Additionally, I would like to integrate a GPS receiver so the robot can return to a home position.

Custom parts and enclosures

Motor guard .STL File

Motor mount .STL File

Schematics

Motor Driver Pinout

Pixy2 Pinout

Code

Dandelion Hunter Code - download the code here

C/C++

 

//GPS not yet implemented
#include <Adafruit_GPS.h>
#include <PWMServo.h>
#include <Pixy2I2C.h>

//define pins
#define WEAPON 2
#define SERVO_PAN 23

#define M1_1 38
#define M1_2 37
#define M2_1 35
#define M2_2 36

#define CENTER_POS 158
#define DEADZONE 10
#define ANGLE_DELAY 10 //number of ms per servo angle

#define TARGET_WIDTH 100 //from 1-316
#define TARGET_HEIGHT 60 //from 1-208

#define GPSSERIAL Serial1
#define GPSECHO false

//create objects
Pixy2I2C pixy;
PWMServo pan_servo;
Adafruit_GPS GPS(&GPSSERIAL);
IntervalTimer gpsTimer;

int largest_size = 0, currently_tracked = 0, servo_pos = 90, servo_flag = -1;
double home_lat, home_lon;

void setup() {
  // put your setup code here, to run once:
  Serial.begin(115200);
  init_pins();
  pixy.init();
  init_GPS();
}

void loop() {
  // put your main code here, to run repeatedly:
  pixy.ccc.getBlocks();
  if(pixy.ccc.numBlocks){
      pixy.ccc.blocks[0].print();
      if(servo_pos != 90){
        if(servo_pos < 90){
          set_motors(20, -20);
          delay((90-servo_pos)*ANGLE_DELAY);
          set_motors(0, 0);
        }
        else if(servo_pos > 90){
          set_motors(-20, 20);
          delay((servo_pos-90)*ANGLE_DELAY);
          set_motors(0, 0);
        }
        servo_pos = 90;
        pan_servo.write(90);
      }
      while(pixy.ccc.blocks[0].m_width * pixy.ccc.blocks[0].m_height < TARGET_HEIGHT * TARGET_WIDTH){
        pixy.ccc.getBlocks();
        if(!pixy.ccc.numBlocks){
          set_motors(0, 0);
          break;
        }
        else{
          pixy.ccc.blocks[0].print();
        if(pixy.ccc.blocks[0].m_x < CENTER_POS-DEADZONE){
          set_motors(-20, 20);
          Serial.println("Moving left");
          delay(50);
          set_motors(0, 0);
        }
        else if((pixy.ccc.blocks[0].m_x >= CENTER_POS-DEADZONE) && (pixy.ccc.blocks[0].m_x <= CENTER_POS+DEADZONE)){
          set_motors(60, 60);
          Serial.println("Moving forward");
          delay(50);
          set_motors(0, 0);
        }
        else if(pixy.ccc.blocks[0].m_x > CENTER_POS+DEADZONE){
          set_motors(20, -20);
          Serial.println("Moving right");
          delay(50);
          set_motors(0, 0);
        }
      }
      }
      if(pixy.ccc.blocks[0].m_width * pixy.ccc.blocks[0].m_height>=TARGET_HEIGHT * TARGET_WIDTH){
        kill_flower();
      }
  }
  else{
    set_motors(0, 0);
    if(!servo_pos){
      servo_flag = 1;
    }
    else if(servo_pos >= 180){
      servo_flag = -1;
    }
    servo_pos += servo_flag;
    pan_servo.write(servo_pos);
  }
}

void kill_flower(){
  set_motors(0, 0);
  set_motors(-60, -60);
  delay(700);
  analogWrite(WEAPON, 255);
  set_motors(80, 80);
  delay(1200);
  set_motors(0, 0);
  analogWrite(WEAPON, 0);
  largest_size = 0;
  currently_tracked = 0;
}

void set_motors(uint8_t m1_val, uint8_t m2_val){
   if(m1_val<0){
      analogWrite(M1_1, 0);
      analogWrite(M1_2, m1_val*-1);
   }
   else{
      analogWrite(M1_1, m1_val);
      analogWrite(M1_2, 0);
   }
   if(m2_val<0){
      analogWrite(M2_1, 0);
      analogWrite(M2_2, m2_val*-1);
   }
   else{
      analogWrite(M2_1, m2_val);
      analogWrite(M2_2, 0);
   }
}

void init_pins(){
  pinMode(M1_1, OUTPUT);
  pinMode(M1_2, OUTPUT);
  pinMode(M2_1, OUTPUT);
  pinMode(M2_2, OUTPUT);
  analogWrite(M1_1, 0);
  analogWrite(M1_2, 0);
  analogWrite(M2_1, 0);
  analogWrite(M2_2, 0);
  pinMode(WEAPON, OUTPUT);
  analogWrite(WEAPON, 0);
  pan_servo.attach(SERVO_PAN);
  pan_servo.write(90);
}

void print_GPS(){
      Serial.print("\nTime: ");
    Serial.print(GPS.hour, DEC); Serial.print(':');
    Serial.print(GPS.minute, DEC); Serial.print(':');
    Serial.print(GPS.seconds, DEC); Serial.print('.');
    Serial.println(GPS.milliseconds);
    Serial.print("Date: ");
    Serial.print(GPS.day, DEC); Serial.print('/');
    Serial.print(GPS.month, DEC); Serial.print("/20");
    Serial.println(GPS.year, DEC);
    Serial.print("Fix: "); Serial.print((int)GPS.fix);
    Serial.print(" quality: "); Serial.println((int)GPS.fixquality);
    if (GPS.fix) {
      Serial.print("Location: ");
      Serial.print(GPS.latitude/100, 5); Serial.print(GPS.lat);
      Serial.print(", ");
      Serial.print(GPS.longitude/100, 5); Serial.println(GPS.lon);
      Serial.print("Speed (knots): "); Serial.println(GPS.speed);
      Serial.print("Angle: "); Serial.println(GPS.angle);
      Serial.print("Altitude: "); Serial.println(GPS.altitude);
      Serial.print("Satellites: "); Serial.println((int)GPS.satellites);
    }
}

void init_GPS(){
  GPS.begin(9600);
  GPS.sendCommand(PMTK_SET_NMEA_OUTPUT_RMCGGA);
  GPS.sendCommand(PMTK_SET_NMEA_UPDATE_1HZ);
  delay(1000);
  GPSSERIAL.println(PMTK_Q_RELEASE);
  char c;
  while(!GPS.newNMEAreceived()){
    c = GPS.read();
  }
  if(GPS.newNMEAreceived()){
    print_GPS();
  }
  //gpsTimer.begin(print_GPS, 3000000);
}

Credits

Arduino “having11” Guy

 Arduino “having11” Guy

 51 projects • 296 followers

 IoT, Arduino, and Raspberry Pi. I can create designs, code, and build projects for only $22/hr. Just message me with your project request.

 

Hackster.io

This content is provided by our content partner Hackster.io, an Avnet developer community for learning, programming, and building hardware. Visit them online for more great content like this.

This article was originally published at Hackster.io. It was added to IoTplaybook or last modified on 07/29/2019.