Simple Multi-mode 4wd Rover JR-001
by jscottb in Circuits > Robots
12973 Views, 151 Favorites, 0 Comments
Simple Multi-mode 4wd Rover JR-001
Small four wheeled bots are fun to build and play with, but offer a new set of challenges for the beginner. With the addition of two more motors, you add several problems, one being more power is needed. This can mean more or larger batteries or at least better batteries. Next, your motor controller choice is a lot more important. You don't want a power thieving h-bridge taking any more power then it really needs to. That being said, you don't want one that can't supply the current needed for all the motors either. In this article, I will try an address all these issues with a 4wd Rover named JR-001.
JR-001 is a simple rover with two modes controlled via a slide switch. Mode one is just the usual obstacle seek-and avoid. Mode two is a keep-away type mode or what I call "Force". With this mode, the bot moves forward and if you place your hand in front of the sensor it will reverse and turn in a curve as it moves backwards. With it, you can pretend you are a Jedi with the force on your side, pushing the bot back!
The two modes just help to serve as a way to demonstrate a simple method to control what your bot does internal and external to code. I must say that my main goal here is help you get a small 4wd bot going with little worries.
What I will cover in building this robot:
- Power considerations for a 4 wheel robot
- H-bridge needed to get the most power and least amount of voltage drop
- Getting power to the wheels without spinning out and gaining more traction as they spin-up
The Software Needed
Things you will need to download:
- The Arduino IDE
- The L9110S motor controller library attached .zip (See the files in this Step)
- The Robot code below or the attached .zip (See the files in this Step)
Hardware Bill of Materials
Parts you will need are as follows:
- Runt Rover Junior chassis or a suitable 4wd replacement
- Arduino Uno or similar board with USB cable
- V5 or V4 or similar sensor shield
- L9110S motor driver (Get a couple as they are cheap)
- S3003 standard size servo
- HC-SR04 or similar ultra sonic sensor
- HC-SR04 holder
- Female to Female jumper wires
- Male to Female jumper wires
- Small breadboard or Mini breadboard
- Small slide switch
- 6 rechargeable AA batteries (NiMH) and charger
- Double sided sticky tape and small zip-ties
- A 'AA' 6 slot battery pack
- Voltage meter (optional, but good to monitor the bots motor voltage)
- 6/32 or M3 screws and nuts x4
-
Battery wire clip holder 4 'AA' batteries
-
Wire connector for 4 AA holder or 9V battery
You can source many of the parts from eBay or other suppliers directly like Sparkfun, RobotShop and ServoCity.
After you have purchased, collected and downloaded everything thing you're ready to start with the build!!!
Assembly - the Chassis
First assemble chassis following the instructions included with the chassis or video. After finishing you should have something like the image.
NOTE: Before going any further, please remove all 4 wheels, so as not to damage the motors whilst putting the other bot components on.
Adding the Servo
Take the standard size servo and place it on the chassis as pictured. You will fasten the servo in with the .5" 6/32 or M3 screws and nuts. The servo spine will be facing the rear of the robot. Now place the largest servo horn you have on the servo and secure it done with the screw included with the servo. Next using double sided tape, zip-ties, place the HC-SR04 holder on the horn as pictured. It will be fully secured later.
Adding the Arduino and Motor Driver
Next place the microcontroller into the clip holder or onto your screw mounts (if you are using another type chassis). Then place the L9110S on the chassis with a standoff or double sided tape as pictured. Place the small or mini breadboard on the chassis next to the servo and motor driver.
After you're done with that it should look something like the picture.
Wiring - the Motors
- A-1A pin on the motor driver to pin 5 on the sensor shield
- A-1B pin on the motor driver to pin 6 on the sensor shield
- B-1A pin on the motor driver to pin 9 on the sensor shield
- B-1B pin on the motor driver to pin 11 on the sensor shield
Now with the robots front facing AWAY from you, take the wires on the LEFT side and -
- Connect the two BLACK wires on the left to the first output screw terminal for Motor A
- Connect the two RED wires on the left to the second output screw terminal for Motor A
Take the wires on the RIGHT side and -
- Connect the two BLACK wires on the left to the first output screw terminal for Motor B
- Connect the two RED wires on the left to the second output screw terminal for Motor B
The Servo:
Now connect the servo wire to pin line 4 on the sensor shield. The white or orange wire is always the signal wire.
Wiring - the Ultrasonic Sensor
Next connect the HC-SR04 up by connecting VCC to one of the 5v (VCC) pin on the sensor shield. The 5v (VCC) pins are the center pins on each row header on the shield. After this is done, connect the GND pin of the sensor to GND pin on the same row (the pin next to the vcc pin). After that, connect the Echo line to pin signal pin 8. Now connect the Trigger line to the the shields pin 3.
Wiring the Mode Switch
NOTE: You will need solder and a soldering iron for this step.
Take three of the Female to Female jumper wires and remove the connector from one end of each wire. Strip back the wires about 3mm long (@1/8th of an inch) and tin them with solder and a soldering iron. Then tin the three terminals on the switch as well. Now solder a wire to each terminal on the switch. See the picture for more details.
After the wires have been soldered on -
- Connect the CENTER wire of the switch to PIN 2 on the sensor shield
- Connect the RIGHT wire of the switch to a VCC line on the sensor shield
- Connect the LEFT wire of the switch to a GND line on the sensor shield
At this point you can attach the wheels and secure the HC-SR04 holder onto the servo horn.
Getting Power to the Bot
- Connect the power select jumper on the sensor shield if it has one
- Connect the GND pin on the L9110s to the GND column on the breadboard
- Connect the VCC pin on the L9110s to the VCC column on the breadboard
- Connect the 4 AA or 9v battery to the 2.1mm power jack of the Arduino
Connect one line from a GND pin on the sensor shield to one pin-hole in a column on the breadboard (not the same as the VCC column!)
Optional - Connect the VCC wire (red) and the GND wire (black) of the volt meter to the respective battery wire; POS to POS, NEG to NEG
Loading the Code
With the wiring checked and rechecked, It's time to load the code. Follow the instructions on the Arduino site on how to install the Arduino IDE.
After the IDE is installed, you can next install the L9110 Motor Driver library. Do this by downloading the zip file included in STEP 1 named L9110Driver.zip and extracting it to your Arduino libraries folder or by following these instructions on how to add a library.
Now that the IDE and Motor driver library have been installed, load the robot code into the Arduino IDE from the zip file found in STEP 1 named multimode.zip.
Connect your PC and Arduino up with the USB cable. Now choose the board from the Tools->Board menu in the IDE, Uno for this project (choose the board you have if different from Uno) Now from Tools->Port menu, pick your com port. After this is done, click the upload button. If all went well, the code was loaded, if not see here for help on the IDE and related issues.
The Bot in Action!
As I said before, this is a simple robot to demonstrate how to more easily construct a 4wd chassis. The kit used is well made and gives you a lot of room to grow and experiment on.
I want to give the budding builder a good starting point for a 4wd bot, and cover issues that come along with it being 4wd.
The Code...
/* Arduino obstacle avoider and follower. Goal in life... Moves forward looking for obstacles to avoid or keep-away from them (Force) Written by Scott Beasley - 2015 Free to use or modify. Enjoy. */ /* Uses the L9110S library. It works with the L9110S h-bridge. See Step 1 for the library code. */ #include <L9110Driver.h> #include <Servo.h> // Defines for the distance reading function of the bot. #define turn() (left_dist >= right_dist) ? go_left () : go_right () #define microsecondsToCentimeters(microseconds) (unsigned long)microseconds / 29.1 / 2.0 #define MIN_ACTION_DIST 20 // 20 cm #define MIN_AWAY_DIST 20 // 20cm from bot // Change these defines if you use different pins #define ECHO_PIN 8 // Digital pin 2 #define TRIGGER_PIN 3 // Digital pin 3 #define SERVO_LR 4 // Pin for left/right scan servo #define pinAIN1 5 // define I1 interface #define pinAIN2 6 // define I2 interface #define pinBIN1 9 // define I3 interface #define pinBIN2 11 // define I4 interface #define MODE_PIN 2 // Bot modes #define MODE_AVOID 0 #define MODE_FORCE 1 // Speed defines #define MAXFORWARDSPEED 225 // Max speed we want moving forward #define MAXBACKWARDSPEED 225 // Max reverse speed #define TOPSPEED 255 // Used to help turn better on carpet and rougher surfaces. // Various time delays used for driving and servo #define TURNDELAY 475 #define BACKUPDELAY 400 #define SERVOMOVEDELAY 200 #define SERVOSEARCHDELAY 85 #define DEBOUNCE_TM 20 // 20ms /* Globals area. */ // Create the motor, servo objects to interface with L9110_Motor motor_left (pinAIN1, pinAIN2); // Create Left motor object L9110_Motor motor_right (pinBIN1, pinBIN2); // Create Right motor object Servo sensor_servo; // Create a servo object for our distance sensor // Global variables byte sweep_pos = 0; // Current position of the sensor servo byte pos_index = 90; byte mode = MODE_AVOID; byte mode_change = true; unsigned long last_pin_change; unsigned long left_dist, right_dist; // Distance measured left and right // function to handle the mode switch with debounce void checkMode ( ) { byte mode_val = digitalRead (MODE_PIN); if ((long)(millis ( ) - last_pin_change) >= DEBOUNCE_TM) { last_pin_change = millis ( ); // Changes the Mode the bot is in. if (mode_val == LOW) mode = MODE_AVOID; if (mode_val == HIGH) mode = MODE_FORCE; mode_change == true; } } void setup ( ) { // Uncomment if you need to debug. //Serial.begin (9600); // Set Serial monitor at 9600 baud //Serial.println ("My Follow/Avoid bot is starting up!"); // Make sure the motors are off at start halt ( ); sensor_servo.attach (SERVO_LR); // Attach the servo SERVO_LR sensor_servo.write (90); // Set the servo to the middle (neutral) pos // Set modes for distance sensor pins pinMode (ECHO_PIN, INPUT); // Set echo pin as input pinMode (TRIGGER_PIN, OUTPUT); // Set trigger pin as output pinMode (MODE_PIN, INPUT); // Set mode pin as input // Delay to give user time to make adjustments. Remove after done. //delay (30000); } void loop ( ) { checkMode ( ); // Read current switch value // If mode has changed, re-center the sensor servo if (mode_change == true) { mode_change = false; pos_index = 90; sweep_pos = 0; sensor_servo.write (pos_index); } if (mode == MODE_AVOID) avoid ( ); // Try not to hit anything! if (mode == MODE_FORCE) force ( ); // keep-away from the hand! } void avoid ( ) { unsigned long dist_fwd; // Rotate the distance sensor as we drive along rotate_sensor ( ); // Give the servo time to get to position to get settled delay (SERVOSEARCHDELAY); // Get a reading from the current sensor direction dist_fwd = ping ( ); //Serial.print ("Distance sensor reading: "); //Serial.println (dist_fwd); // Go forward while nothing is in the distance sensors read area if (dist_fwd > MIN_ACTION_DIST || dist_fwd == 0) { go_forward ( ); } else // There is something in the sensors read area { halt ( ); // Stop! go_backward ( ); // Back up a bit delay (BACKUPDELAY); halt ( ); // Stop! sensor_read ( ); // Read distance left and right turn ( ); // Turn toward the clearest path delay (TURNDELAY); halt ( ); // Reset to mid position before moving again pos_index = 90; sweep_pos = 0; sensor_servo.write (pos_index); } } void force ( ) { unsigned long obj_dist; obj_dist = ping ( ); //Serial.print ("Distance sensor reading: "); //Serial.println (obj_dist); // Go forward while nothing is in the distance sensors read area if (obj_dist >= (MIN_AWAY_DIST + 20)) { halt ( ); go_forward ( ); } else if (obj_dist <= (MIN_AWAY_DIST + 5)) { halt ( ); go_backward ( ); // Reset to mid position before moving again pos_index = 90; sweep_pos = 0; sensor_servo.write (pos_index); } delay (250); } // Read the sensor after we find something in the way. This helps find a new // path void sensor_read ( ) { //Serial.println ("Server at 40 deg..."); sensor_servo.write (40); delay (SERVOMOVEDELAY); right_dist = ping ( ); //Look to the right //Serial.println ("Servo at 140 deg..."); sensor_servo.write (140); delay (SERVOMOVEDELAY); left_dist = ping ( ); // Look to the left // Set the servo back to the center pos //Serial.println ("Servo at 90 deg..."); sensor_servo.write (90); } // Rotate the sensor servo at 45deg increments void rotate_sensor ( ) { if (sweep_pos <= 0) { pos_index = 45; } else if (sweep_pos >= 180) { pos_index = -45; } //Serial.print ("pos_index = "); //Serial.println (pos_index); sweep_pos += pos_index; //Serial.print ("sweep_pos = "); //Serial.println (sweep_pos); sensor_servo.write (sweep_pos); } // Read the HC-SR04 uSonic sensor unsigned long ping ( ) { // Trigger the uSonic sensor (HC-SR04) to send out a ping digitalWrite (TRIGGER_PIN, LOW); delayMicroseconds (5); digitalWrite (TRIGGER_PIN, HIGH); delayMicroseconds (10); digitalWrite (TRIGGER_PIN, LOW); // Measure how long the ping took and convert to cm's return (microsecondsToCentimeters (pulseIn (ECHO_PIN, HIGH))); } void go_forward ( ) { //Serial.println ("Going forward..."); // Ramp the motors up to the speed. // Help with spining out on some surfaces and ware and tare on the GM's ramp_it_up (MAXFORWARDSPEED, FORWARD, BACKWARD); // Set to all of the set speed just incase the ramp last vat was not all of // it. motor_left.setSpeed (MAXFORWARDSPEED); motor_right.setSpeed (MAXFORWARDSPEED); motor_left.run (FORWARD|RELEASE); motor_right.run (BACKWARD|RELEASE); } void go_backward ( ) { //Serial.println ("Going backward..."); // Ramp the motors up to the speed. // Help with spining out on some surfaces and ware and tare on the GM's ramp_it_up (MAXBACKWARDSPEED, BACKWARD, FORWARD); // Set to all of the set speed just incase the ramp last vat was not all of // it. motor_left.setSpeed (MAXBACKWARDSPEED); motor_right.setSpeed (MAXBACKWARDSPEED); motor_left.run (BACKWARD|RELEASE); motor_right.run (FORWARD|RELEASE); } void go_left ( ) { //Serial.println ("Going left..."); // Ramp the motors up to the speed. // Help with spining out on some surfaces and ware and tare on the GM's ramp_it_up (TOPSPEED, BACKWARD, BACKWARD); // Set to all of the set speed just incase the ramp last vat was not all of // it. motor_left.setSpeed (TOPSPEED); motor_right.setSpeed (TOPSPEED); motor_left.run (BACKWARD|RELEASE); motor_right.run (BACKWARD|RELEASE); } void go_right ( ) { //Serial.println ("Going right..."); // Ramp the motors up to the speed. // Help with spining out on some surfaces and ware and tare on the GM's ramp_it_up (TOPSPEED, FORWARD, FORWARD); // Set to all of the set speed just incase the ramp last vat was not all of // it. motor_left.setSpeed (TOPSPEED); motor_right.setSpeed (TOPSPEED); motor_left.run (FORWARD|RELEASE); motor_right.run (FORWARD|RELEASE); } void halt ( ) { //Serial.println ("Halt!"); motor_left.setSpeed (0); motor_right.setSpeed (0); motor_left.run (BRAKE); motor_right.run (BRAKE); } void ramp_it_up (uint8_t speed, uint8_t lf_dir, uint8_t rt_dir) { uint8_t ramp_val = 0, step_val = 0; step_val = abs (speed / 4); for (uint8_t i = 0; i < 4; i++) { ramp_val += step_val; motor_left.setSpeed (ramp_val); motor_right.setSpeed (ramp_val); motor_left.run (lf_dir|RELEASE); motor_right.run (rt_dir|RELEASE); delay (25); } }
Code Details - Switch Debounce
Here we will cover some of the more interesting aspects of the code.
Handling of the mode Switch:
Mechanical switches like push buttons and toggle switches are very nice and convenient to use with a micro controller, but have a big problem... They generate a lot of electrical noise when switched or pressed (See the picture for details). This "noise" wreaks havoc with your code to read a button or switch and can cause the code to mis-read a change in the switch state. To remedy this, we add some logic to "debounce" the switch.
A debounce is just a little extra code to keep track of the last time a change in the switch state happened. When the switch is toggled or switched, the function that reads the digital pin for it, saves the time the change occurred. When another change is noted, that saved time is checked to see if it's out of range of the new time. If the new change is less than 20ms from the last one, the change is ignored and assumed to be noise. If it was greater than 20ms, then the change is taken and a new change time is saved. This keeps the code from getting multiple changes per switch flip.
// From the Global section.... #define DEBOUNCE_TM 20 // 20ms . unsigned long last_pin_change; . . . // function to handle the mode switch with debounce void checkMode ( ) { byte mode_val = digitalRead (MODE_PIN); if ((long)(millis ( ) - last_pin_change) >= DEBOUNCE_TM) { last_pin_change = millis ( ); // Changes the Mode the bot is in. if (mode_val == LOW) mode = MODE_AVOID; if (mode_val == HIGH) mode = MODE_FORCE; mode_change == true; } }
Code Details - Motor Ramping
Early on I stated one of the issues with a 4wd robot is motor traction. This is counter intuitive when you first think about it. 4wd cars and trucks have a lot better traction and power distribution than a 2wd vehicle. This is true, but in the robot's case there is a motor on EACH wheel and they are not completely matched in speed and power as they turn on. It's kind of like flooring a car in sandy or wet conditions and skidding out. This causes the bot to skid a bit and not gain traction on some surfaces. This is noticed a lot in turning on a 4 wheel vs. a 2 wheel chassis, where traction and power is lost a lot.
To help with this (but not totally eliminate it) , I have a function that each motor movement function calls. This function "ramps" up the motor(s) to the speed given by doing a divide of the speed by 4 and looping 4 times adding the quotient of the division by 4 to each step in the loop. This allows for a slow build up of the set speed and gives better traction, tracking (some-what) and reduces wear-and-tear on the gear motors.
void ramp_it_up (uint8_t speed, uint8_t lf_dir, uint8_t rt_dir) { uint8_t ramp_val = 0, step_val = 0; step_val = abs (speed / 4); for (uint8_t i = 0; i < 4; i++) { ramp_val += step_val; motor_left.setSpeed (ramp_val); motor_right.setSpeed (ramp_val); motor_left.run (lf_dir|RELEASE); motor_right.run (rt_dir|RELEASE); delay (25); } }
With this method, you could do the same thing in reverse for ramping down to stops. Ramping also helps on other gear motors, not just the ones like this bot has. I highly recommend using this on larger bots as well.
The Motor Driver Choice
For this robot, the two extra motors per side does add to the power (amps) pull from the batteries and also adds stress on the motor driver as well. To help the bot along, you want to choose a driver that will not die if all the motors are stalled (STOPPED) and one that will not drop a lot of voltage as it powers the motors.
For the motors on the Actobotics Junior as well as many other small bots, the L9110s driver will work out nicely. It has a continuous power output of around the maximum of the gear motors used on this bot. This motor driver is also VERY inexpensive and easy to drive from code, and with a minimum amount of pins used on the micro controller. For this project, they are the best value I think, though there are other options you can choose from like the TB6612FNG driver as well as others (please NOT the L293 or L298!).