I've written code for an Arduino library to abstract away some of the underlying logic in a particular way of moving robots. Code is posted after explanations.
I'm not assuming a high degree of familiarity here so, I'll include abstracts from the README that will allow you to understand what the code is trying to do, and how its structured. If needed, I've taken the following code from its public repository at commit 5e25311 and you can view a more detailed README there.
This library provides an interface to easily control a differential wheeled robot using an Arduino-compatible microcontroller.
The expectation is that you have an even number of motors, with one half on either side of a structure that looks like a rover or a car. You then control the direction the robot moves in by controlling which motors turn on and in which direction. For example, if the left motors spin forward and the right motors spin backwards, the robot will rotate clockwise.
Types of robots offered by the library
There are classes for two kinds of robots included in this library.
Standard Differential Drive Robot
This is a normal differential drive robot, with motors on each side. It is what we described above: the direction is controlled by controlling which motors turn on and in which direction.
The class for this is
DDBot
.Forward-biased Differential Drive Robot
The physical robot for this can be the same as a standard differential drive robot, but the control is different. In this case, the robot's motor's are always set to move forward. The direction is controlled by varying the speeds of the motors. In certain cases (like line followers), it is expected that this library leads to more less jerky motion of the robot.
The class for this is
ForwardDDBot
.
Different files
File name and location | Purpose |
---|---|
DDBot.h |
Header file for declaring classes and inheritance. |
DDBot.cpp |
Source for function definitions. |
examples/Square/Square.ino |
Example 1 of using the library. |
examples/SpeedTuner/SpeedTuner.ino |
Example 2 of using the library. |
DDBot.h
#include <Arduino.h>
#ifndef DDBot_h
#define DDBot_h
/* Differential Drive Bot
This is a conventional **differential wheeled robot**, and direction is set by varying which motors spin
and in which direction.
Here, speed control of a motor simply controls how fast the robot moves. The expected use of this library
just involves the user calling the method of the appropriate direction and/or speed in their main loop.
*/
class DDBot {
private:
public:
// the sequence of pins is important, and is used throughout the library
// the first two pins are for the left motor, and the second two are for the right motor
// the first pin in each pair is for the forward direction, and the second is for the backward direction
uint8_t directionPins[4];
// when each motor has a dedicated PWM pin, it's possible to control the speed of each motor independently
// in other cases, the speed of each motor is controlled by the same PWM pin
// two declarations exist to accommodate both cases, and this pattern is used throughout the library
uint8_t PWMPins[2] = {0, 0};
uint8_t PWMPin = 0;
// the constructors are responsible for setting the pin numbers from the arguments to the class properties
DDBot(); // allow the user to directly set the arrays
DDBot(uint8_t directionPins[4]);
DDBot(uint8_t directionPins[4], uint8_t PWMPins[2]);
DDBot(uint8_t directionPins[4], uint8_t PWMPin);
void setPinModes();
void setSpeed(uint8_t speed);
void setSpeed(uint8_t leftSpeed, uint8_t rightSpeed);
void writeDirections(bool leftForward, bool leftBackward, bool rightForward, bool rightBackward);
void writeDirections(bool leftForward, bool leftBackward, bool rightForward, bool rightBackward, uint8_t speed);
void writeDirections(bool leftForward, bool leftBackward, bool rightForward, bool rightBackward, uint8_t leftSpeed, uint8_t rightSpeed);
void forward();
void forward(uint8_t speed);
void forward(uint8_t leftSpeed, uint8_t rightSpeed);
void backward();
void backward(uint8_t speed);
void backward(uint8_t leftSpeed, uint8_t rightSpeed);
void left();
void left(uint8_t speed);
void left(uint8_t leftSpeed, uint8_t rightSpeed);
void right();
void right(uint8_t speed);
void right(uint8_t leftSpeed, uint8_t rightSpeed);
void clockwise();
void clockwise(uint8_t speed);
void clockwise(uint8_t leftSpeed, uint8_t rightSpeed);
void counterClockwise();
void counterClockwise(uint8_t speed);
void counterClockwise(uint8_t leftSpeed, uint8_t rightSpeed);
// it doesn't make sense to specify a PWM variant of a stop command
void stop();
~DDBot();
};
/* Forward Differential Drive Bot
This is a **differential wheeled robot** with a forward bias, and direction is set by varying just the
speed of each motor. So while the classic differential drive bot can use the same physical structure,
the functionality is different. For example, a forward differential drive bot can't turn in place or
move backwards.
This class was built for line following robots, where the robot is biased towards moving forward, and
just incrementally varying the speed provides smoother control than the classic differential drive bot.
This is important to ensure that the robot doesn't overshoot and lose track of the line.
Conventionally, the direction of a differential wheeled robot is set by turning motors on
or off in a specific direction. In this setup, they are set to move forward perpetually, and
direction control is achieved by varying the speed of each motor instead. This allows for
smoother turns and will (hopefully) help the robot to stay on its line more reliably.
The main loop should be structured to use feedback control. To use this library, the user should call
the method of the appropriate direction when they want, and call the `write()` method once per loop.
This will update the speed of each motor to come closer to the target speed. This is done to avoid
sudden changes in speed. A delay might have to be added to the main loop to allow the changes to
propagate.
*/
class ForwardDDBot : public DDBot {
private:
// this is the value used to "slow down" a motor
// it is used when we want to turn, but don't want to set the speed of the other motor to 0
// this will be calculated in the init() method by multiplying the maxPWM by the adjustment factor
uint8_t _adjustedPWM;
// "actual" values are written to a motor during a given call of the write() method
// "target" values are the values that the user wants to write to a motor
// in each call of the write() method, the actual values are adjusted to come closer to the target values
uint8_t leftActualPWM, leftTargetPWM, rightActualPWM, rightTargetPWM;
public:
uint8_t maxPWM; // maximum "speed" for each motor
float adjustment; // scaling factor used when slowing down a given motor
// the constructors are responsible for setting the pin numbers from the arguments to the class properties
ForwardDDBot(); // allow the user to directly set the arrays or the default PWM values
ForwardDDBot(uint8_t maxPWM, float adjustment);
ForwardDDBot(uint8_t directionPins[4], uint8_t PWMPins[2]);
ForwardDDBot(uint8_t directionPins[4], uint8_t PWMPins[2], uint8_t maxPWM, float adjustment);
void init();
void calculateAdjustedPWM();
void left();
void centre();
void right();
void stop();
// recall that rotations and moving backwards are not possible with this type of robot
// so these methods are not implemented
void write();
~ForwardDDBot();
};
#endif // DDBot_h
DDBot.cpp
#include "DDBot.h"
#include <Arduino.h>
#ifndef DDBot_cpp
#define DDBot_cpp
DDBot::DDBot() {}
DDBot::~DDBot() {}
DDBot::DDBot(uint8_t directionPins[4]) {
for (size_t i = 0; i < 4; i++) {
this->directionPins[i] = directionPins[i];
}
}
DDBot::DDBot(uint8_t directionPins[4], uint8_t PWMPins[2]) {
for (size_t i = 0; i < 4; i++) {
this->directionPins[i] = directionPins[i];
}
for (size_t i = 0; i < 2; i++) {
this->PWMPins[i] = PWMPins[i];
}
}
DDBot::DDBot(uint8_t directionPins[4], uint8_t PWMPin) {
for (size_t i = 0; i < 4; i++) {
this->directionPins[i] = directionPins[i];
}
this->PWMPin = PWMPin;
}
void DDBot::setPinModes() {
for (size_t i = 0; i < 4; i++) {
pinMode(directionPins[i], OUTPUT);
}
// do not set pinMode(s) for PWM pin(s) if they are not set (i.e. if they are 0)
if (PWMPins[0] != 0 && PWMPins[1] != 0) {
for (size_t i = 0; i < 2; i++) {
pinMode(PWMPins[i], OUTPUT);
}
}
if (PWMPin != 0) {
pinMode(PWMPin, OUTPUT);
}
}
void DDBot::setSpeed(uint8_t speed) {
// do not set speed for PWM pin(s) if they are not set (i.e. if they are 0)
if (PWMPins[0] != 0 && PWMPins[1] != 0) {
analogWrite(PWMPins[0], speed);
analogWrite(PWMPins[1], speed);
}
if (PWMPin != 0) {
analogWrite(PWMPin, speed);
}
}
void DDBot::setSpeed(uint8_t leftSpeed, uint8_t rightSpeed) {
analogWrite(PWMPins[0], leftSpeed);
analogWrite(PWMPins[1], rightSpeed);
}
void DDBot::writeDirections(bool leftForward, bool leftBackward, bool rightForward, bool rightBackward) {
digitalWrite(directionPins[0], leftForward);
digitalWrite(directionPins[1], leftBackward);
digitalWrite(directionPins[2], rightForward);
digitalWrite(directionPins[3], rightBackward);
}
void DDBot::writeDirections(bool leftForward, bool leftBackward, bool rightForward, bool rightBackward, uint8_t speed) {
digitalWrite(directionPins[0], leftForward);
digitalWrite(directionPins[1], leftBackward);
digitalWrite(directionPins[2], rightForward);
digitalWrite(directionPins[3], rightBackward);
setSpeed(speed);
}
void DDBot::writeDirections(bool leftForward, bool leftBackward, bool rightForward, bool rightBackward, uint8_t leftSpeed, uint8_t rightSpeed) {
digitalWrite(directionPins[0], leftForward);
digitalWrite(directionPins[1], leftBackward);
digitalWrite(directionPins[2], rightForward);
digitalWrite(directionPins[3], rightBackward);
setSpeed(leftSpeed, rightSpeed);
}
// the following methods set the direction of the robot by controlling which
// direction motors are going to turn and in which direction
// you can figure out these methods by trying to imagine the robot, maybe
// using a model or a drawing
void DDBot::forward() {
writeDirections(HIGH, LOW, HIGH, LOW);
}
void DDBot::forward(uint8_t speed) {
writeDirections(HIGH, LOW, HIGH, LOW, speed);
}
void DDBot::forward(uint8_t leftSpeed, uint8_t rightSpeed) {
writeDirections(HIGH, LOW, HIGH, LOW, leftSpeed, rightSpeed);
}
void DDBot::backward() {
writeDirections(LOW, HIGH, LOW, HIGH);
}
void DDBot::backward(uint8_t speed) {
writeDirections(LOW, HIGH, LOW, HIGH, speed);
}
void DDBot::backward(uint8_t leftSpeed, uint8_t rightSpeed) {
writeDirections(LOW, HIGH, LOW, HIGH, leftSpeed, rightSpeed);
}
void DDBot::left() {
writeDirections(LOW, HIGH, LOW, LOW);
}
void DDBot::left(uint8_t speed) {
writeDirections(LOW, HIGH, LOW, LOW, speed);
}
void DDBot::left(uint8_t leftSpeed, uint8_t rightSpeed) {
writeDirections(LOW, HIGH, LOW, LOW, leftSpeed, rightSpeed);
}
void DDBot::right() {
writeDirections(LOW, LOW, LOW, HIGH);
}
void DDBot::right(uint8_t speed) {
writeDirections(LOW, LOW, LOW, HIGH, speed);
}
void DDBot::right(uint8_t leftSpeed, uint8_t rightSpeed) {
writeDirections(LOW, LOW, LOW, HIGH, leftSpeed, rightSpeed);
}
void DDBot::clockwise() {
writeDirections(LOW, HIGH, HIGH, LOW);
}
void DDBot::clockwise(uint8_t speed) {
writeDirections(LOW, HIGH, HIGH, LOW, speed);
}
void DDBot::clockwise(uint8_t leftSpeed, uint8_t rightSpeed) {
writeDirections(LOW, HIGH, HIGH, LOW, leftSpeed, rightSpeed);
}
void DDBot::counterClockwise() {
writeDirections(HIGH, LOW, LOW, HIGH);
}
void DDBot::counterClockwise(uint8_t speed) {
writeDirections(HIGH, LOW, LOW, HIGH, speed);
}
void DDBot::counterClockwise(uint8_t leftSpeed, uint8_t rightSpeed) {
writeDirections(HIGH, LOW, LOW, HIGH, leftSpeed, rightSpeed);
}
void DDBot::stop() {
writeDirections(LOW, LOW, LOW, LOW);
}
ForwardDDBot::ForwardDDBot() {}
ForwardDDBot::~ForwardDDBot() {}
ForwardDDBot::ForwardDDBot(uint8_t maxPWM, float adjustment) {
this->maxPWM = maxPWM;
this->adjustment = adjustment;
}
ForwardDDBot::ForwardDDBot(uint8_t directionPins[4], uint8_t PWMPins[2]) {
for (size_t i = 0; i < 4; i++) {
this->directionPins[i] = directionPins[i];
}
for (size_t i = 0; i < 2; i++) {
this->PWMPins[i] = PWMPins[i];
}
}
ForwardDDBot::ForwardDDBot(uint8_t directionPins[4], uint8_t PWMPins[2], uint8_t maxPWM, float adjustment) {
for (size_t i = 0; i < 4; i++) {
this->directionPins[i] = directionPins[i];
}
for (size_t i = 0; i < 2; i++) {
this->PWMPins[i] = PWMPins[i];
}
this->maxPWM = maxPWM;
this->adjustment = adjustment;
}
void ForwardDDBot::calculateAdjustedPWM() {
// this is the value used to "slow down" a motor
// it is used when we want to turn, but don't want to set the speed of the other motor to 0
_adjustedPWM = this->maxPWM * this->adjustment;
}
void ForwardDDBot::init() {
setPinModes();
calculateAdjustedPWM();
this->leftActualPWM = maxPWM;
this->rightActualPWM = maxPWM;
// set the robot to perpetually move forward
forward(leftActualPWM, rightActualPWM);
}
// the following methods set the direction of the robot by controlling which
// motor spins faster and which one slower
// the logic here is the same as in the conventional differential drive robot
// and you can figure it out the same way
void ForwardDDBot::left() {
leftTargetPWM = _adjustedPWM;
rightTargetPWM = maxPWM;
}
void ForwardDDBot::right() {
leftTargetPWM = maxPWM;
rightTargetPWM = _adjustedPWM;
}
void ForwardDDBot::centre() {
leftTargetPWM = maxPWM;
rightTargetPWM = maxPWM;
}
void ForwardDDBot::stop() {
leftTargetPWM = 0;
rightTargetPWM = 0;
}
void ForwardDDBot::write() {
// update "actual" PWM values so that they get closer to the "target" values
// this will eventually get the values to equal, as exponential decay (but
// with integer division)
leftActualPWM = (leftTargetPWM + leftActualPWM) / 2;
rightActualPWM = (rightTargetPWM + rightActualPWM) / 2;
// write the actual PWM values to the motor driver
analogWrite(PWMPins[0], leftActualPWM);
analogWrite(PWMPins[1], rightActualPWM);
}
#endif // DDBot_cpp
examples/Square/Square.ino
/* Square
This example shows how to use the DDBot library to make the robot move in a square.
The robot will move forward for 2 seconds, then turn right for 1 second, then move forward for 2 seconds, then turn right for 1 second, and so on.
The circuit:
* Pin 2 to left motor forward
* Pin 3 to left motor backward
* Pin 4 to right motor forward
* Pin 5 to right motor backward
* Pin 10 to left motor speed
* Pin 11 to right motor speed
*/
#include <Arduino.h>
#include <DDBot.h>
// define the pins used by the motors
uint8_t directionPins[4] = {2, 3, 4, 5};
uint8_t speedPins[2] = {10, 11};
// create an instance of the DDBot class
// this allows you to potentially control multiple robots at once using multiple instances
DDBot bot(directionPins, speedPins);
void setup() {
// set the pin modes for the motor DIO pins
bot.setPinModes();
}
void loop() {
// move forward with full speed for 2 seconds
bot.forward(255);
delay(2000);
// turn right with full speed for 1 second
bot.right(255);
delay(1000);
}
examples/SpeedTuner/SpeedTuner.ino
/* SpeedTuner
This example shows how to use the DDBot library to figure out the right values for tuning
PWM parameters for use with the Forward-biased Differential Drive robot.
The robot will start with 0 adjustment. It will then increase the adjustment by
0.05, attempt to forward for 3 seconds, and then increase the adjustment by 0.05 again.
This process repeats until the adjustment is 1.0, at which point the robot will stop.
It will then repeat the process while going backwards, increasing the adjustment from 0 to
1.0 in 0.05 increments.
At each stage, it will output the adjustment value and the adjusted PWM to the Serial monitor,
so that you can determine when it has sufficient PWM to accelerate.
The circuit:
* Pin 2 to left motor forward
* Pin 3 to left motor backward
* Pin 4 to right motor forward
* Pin 5 to right motor backward
* Pin 10 to left motor speed
* Pin 11 to right motor speed
*/
#include <Arduino.h>
#include <DDBot.h>
// define the pins used by the motors
uint8_t directionPins[4] = {2, 3, 4, 5};
uint8_t speedPins[2] = {10, 11};
// create an instance of the DDBot class
// this allows you to potentially control multiple robots at once using multiple instances
ForwardDDBot bot(directionPins, speedPins);
void setup() {
// use the maximum possible PWM value
bot.maxPWM = 255;
// set the pin modes for the motor DIO pins
bot.init();
// initialize the adjustment value
bot.adjustment = 0.0;
// open the Serial connection
Serial.begin(115200);
bot.forward();
// 1 / 0.05 per increment = 20 increments, so we have 20 iterations
for (int counter = 0; counter < 20; counter++) {
bot.adjustment += 0.05;
bot.calculateAdjustedPWM();
Serial.print("[Forward] Adjustment: ");
Serial.print(bot.adjustment);
Serial.print(", Adjusted PWM: ");
Serial.println(bot.maxPWM * bot.adjustment);
// we need to update the PWM values to allow the feedback loop to work
// we do this by calling the write() method 300 times, with a 10 ms delay between each call,
// which is a total of 3 seconds
for (int i = 0; i < 300; i++) {
delay(10);
bot.write();
}
}
// stop the robot for 5 seconds
bot.stop();
delay(5000);
// reset the adjustment value
bot.adjustment = 0.0;
bot.backward();
// again, we have 20 iterations, for a total of 1.0 adjustment over 3 seconds
for (int counter = 0; counter < 20; counter++) {
bot.adjustment += 0.05;
bot.calculateAdjustedPWM();
Serial.print("[Backward] Adjustment: ");
Serial.print(bot.adjustment);
Serial.print(", Adjusted PWM: ");
Serial.println(bot.maxPWM * bot.adjustment);
// again, we need to update the PWM values to allow the feedback loop to work
// so 300 iterations * 10 ms per iteration = 3 seconds
for (int i = 0; i < 300; i++) {
delay(10);
bot.write();
}
}
bot.stop();
}
void loop() {}
```