Electronics Software Development

Driving A DC Motor With An Arduino

Arduino Motor Graphic
Written by John Woolsey

Skill Level: Intermediate

Table Of Contents

Introduction

This tutorial will teach you how to drive small DC motors with your Arduino Uno. Specifically, we will cover

  • Connecting a motor driver board and two motors to an Arduino Uno,
  • Constructing a distinct Motor class that interfaces with the motor driver board to provide various methods for controlling a motor,
  • Using the Motor class in a sketch to perform basic operations,
  • Enhancing the sketch by checking for any faults that may be occurring on the motor driver,
  • Showing how to ramp up and down a motor’s speed over a specific time frame, and
  • Demonstrating how to provide variable speed control using a potentiometer.

A basic understanding of electronics and programming is expected along with some familiarity with the Arduino platform. If you are new to Arduino, or would just like to refresh your knowledge, please see our Blink: Making An LED Blink On An Arduino Uno tutorial before proceeding with this one.

This tutorial uses a solderless breadboard to build a circuit from a schematic diagram. The All About Circuit’s Understanding Schematics, SparkFun’s How to Read a Schematic, Core Electronics’ How to Use Breadboards, and Science Buddies’ How to Use a Breadboard guides are good resources for learning how to translate a schematic to a breadboard.

The resources created for this tutorial are available on GitHub for your reference.

What Is Needed

  • Linux, macOS, Or Windows Based Computer With A USB Port
  • Arduino IDE
  • Arduino Uno (R3 available on Arduino and SparkFun; WiFi Rev2 on Arduino and SparkFun) With Compatible USB Cable
  • Soldering Station (for attaching headers to motor driver board)
  • Solderless Breadboard (available on Adafruit and SparkFun)
  • Preformed Breadboard Jumper Wire Kit (available on SparkFun and CanaKit)
  • 9 x Male/Male Jumper Wires (available on Adafruit and Arrow)
  • 6 x Alligator Clip Test Leads (available on Adafruit and SparkFun)
  • Adafruit DRV8833 DC/Stepper Motor Driver Breakout Board (available on Adafruit and Digi-Key)
  • 1 or 2 DC Motors (available on Adafruit and PiShop) with optional propellers (available on Adafruit) and motor mounts (available on Adafruit)
  • 10 KΩ Linear Potentiometer (available on Adafruit and Sparkfun)
  • 6-9 Volt Power Supply, e.g. A Benchtop Power Supply (recommended) Or A 9V Battery With Corresponding Battery Clip (available on Digi-Key and Jameco)
  • Small Flat Head Screwdriver

Background Information

You can find many designs, integrated circuit chips, and breakout boards used for controlling motors when searching the internet. Almost all of them are based on the H-bridge circuit that uses four transistors to drive a motor forward as well as in reverse.

I initially set out to write an article on how to build your own custom H-bridge based motor controller and interface it to your Arduino board. However, my research showed that modeling a motor in a circuit simulator is not an easy task and requires specialized test equipment that I do not have. Since I wanted to provide the appropriate details to back up my design choices, this route proved insufficient. I chose instead to use a pre-built motor controller breakout board. Going this route also has the benefits of providing extra features, e.g. over current and fault detection, that I was not planning to add to the original design.

After researching the motor controller boards available, I chose the Adafruit DRV8833 DC/Stepper Motor Driver Breakout Board based on the DRV8833 Dual H-Bridge Motor Driver chip from Texas Instruments. This breakout board has the ability to drive two DC motors (including speed control with PWM) or one stepper motor. It also includes reverse EMF and over current protection circuitry. The board also supports a motor supply voltage range of 2.7 – 10.8 V and an input logic range of 2.7 – 5.75 V.

I am using the Arduino Uno WiFi Rev2 development board connected to a macOS based computer running the desktop Arduino IDE. If you are using a different Arduino board or computer setup, the vast majority of this tutorial should still apply, however, some minor changes may be necessary.

If you need assistance with your particular setup, post a question in the comments section below and I, or someone else, can try to help you.

Building The Circuit

Before connecting any circuitry to your Arduino board, disconnect it from power and your computer. This avoids accidental damage during wiring.

The headers of the Adafruit DRV8833 DC/Stepper Motor Driver Breakout Board are not pre-attached and need to be soldered onto the board in order to easily attach the breakout board to a solderless breadboard.

Place the components and wire up the circuit on a breadboard according to the schematic diagram shown below.

Schematic Diagram Of The Motor Driver Circuit Connected To An Arduino Uno
Schematic Diagram Of The Motor Driver Circuit Connected To An Arduino Uno

If you have a limited number of Arduino GPIO pins available in your project, you can save a couple of pins without losing too much functionality. If you are not interested in directly enabling and disabling the motor driver, you can tie the SLP pin on the driver board high to leave the driver always enabled. Likewise, the board’s FLT pin can remain disconnected if you do not care about checking for driver faults.

A 9V battery can be used instead of a benchtop power supply to power the motor(s), but it may only be able to drive one motor at a time and the battery could drain quickly. Alternatively, four AA batteries in series could also be used providing a total voltage of 6 volts, but this will drastically limit your speed control capability.

The circuit should look similar to the one shown below once completed.

Completed Motor Driver Circuit Connected To An Arduino Uno
Completed Motor Driver Circuit Connected To An Arduino Uno

As you can see, I added some propellers to my motors to make it easier for me to see when the motors are active and at what speed they are running.

Once the circuit is built, connect your Arduino to your computer with the USB cable.

Creating The Sketch

Open the Arduino IDE and create a sketch named Motors. Leave the default code in the sketch for now as we will update it later.

Creating The Motor Class

We will be creating and utilizing a Motor class that will handle all of the control mechanisms for driving a motor with the Adafruit DRV8833 DC/Stepper Motor Driver Breakout Board. Although this class was written for use with this specific breakout board in mind, it is a generic design that should still work across a wide range of motor driver boards and circuits.

Create two new tabs (files) within the Arduino IDE named Motor.h and Motor.cpp in the Motors sketch. These will be the header and implementation files respectively for the new Motor class. To create new files within a sketch, click the down arrow on the right side of the tab bar (the bar containing the Motors.ino filename), and select New Tab from the drop down menu. Once all of the files are created, you should now see three tabs (files) in your Arduino IDE named Motors.ino, Motor.h, and Motor.cpp.

Open the Motor.h tab and add the code shown below.

#ifndef Motor_H
#define Motor_H

#include "Arduino.h"

enum MotorCommand {Forward, Reverse, Brake, Coast};  // available motor commands

class Motor {
   char _name[21];         // motor name (20 characters max)
   MotorCommand _command;  // motor command
   uint8_t _speed;         // motor speed (percentage of maximum speed)
   uint8_t _posPin;        // motor driver positive input control pin
   uint8_t _negPin;        // motor driver negative input control pin

   void _driveMotor();  // drive motor with current command and speed attributes

 public:
   Motor(uint8_t posPin, uint8_t negPin, MotorCommand command = Coast, uint8_t speed = 0, const char* name = "Unknown");  // motor instance constructor
   const char* name();  // get motor name
   void setName(const char* name);  // set motor name
   MotorCommand command();  // get motor command
   void setCommand(MotorCommand command);  // set motor command
   uint8_t speed();  // get motor speed
   void setSpeed(uint8_t speed);  // set motor speed
   void drive(MotorCommand command = Forward, uint8_t speed = 100);  // drive motor with specified attributes
   void stop(MotorCommand command = Coast);  // stop motor with specified command
};

#endif

The MotorCommand enumeration on line 6 defines the available commands that we can give the motor. The Forward and Reverse commands drive the motor forward or in reverse. The Coast command tells the motor to passively coast to a stop. The Brake command actively brakes the motor, quickly bringing it to a stop.

Lines 9-13 define the Motor class’ private member variables. These are the attributes that are used to describe and control a specific motor instance. The _ prefix denotes that the attributes are private members of the class.

The private _driveMotor() method, declared on line 15, is used to send control signals directly to the motor driver. It is used internally by many of the class’ public methods.

Lines 18-26 declare the public methods that will be available for us to use in our sketch. The first, on line 18, is the motor instance constructor, Motor(), that is used to create and configure a new motor instance. It also defines the default values for the command, speed, and name attributes if they are not specified. The specified attributes are actively applied upon creation. For instance, if the command and speed arguments of Reverse and 50 are specified, the motor will immediately move in reverse at 50% throttle.

Lines 19-24 consist of methods for retrieving and setting the various attributes of the motor, i.e. the motor’s name, current command, and speed.

The drive() method, on line 25, drives the motor with the optional command and speed arguments if specified. If no arguments are specified, this method will drive the motor forward at 100% throttle. It is expected that only the Forward and Reverse commands will be used with this method, but it can appropriately handle the Coast and Brake commands as well.

The stop() method, on line 26, stops the motor with the optional command argument if specified. If no argument is specified, the motor will coast to a stop. It is expected that only the Coast and Brake commands will be used with this method, but if any command other than Brake is specified, the method will default to Coast. For instance, if the Forward or Reverse command is specified, the Coast command will be set instead.

Open the Motor.cpp tab and add the code shown below.

#include "Motor.h"

Motor::Motor(uint8_t posPin, uint8_t negPin, MotorCommand command, uint8_t speed, const char* name) {
   _posPin = posPin;
   _negPin = negPin;
   _command = command;
   _speed = speed;
   strcpy(_name, name);
   pinMode(posPin, OUTPUT);
   pinMode(negPin, OUTPUT);
   _driveMotor();
}

const char* Motor::name() { return _name; }

void Motor::setName(const char* name) { strcpy(_name, name); }

MotorCommand Motor::command() { return _command; }

void Motor::setCommand(MotorCommand command) {
   _command = command;
   _driveMotor();
}

uint8_t Motor::speed() { return _speed; }

void Motor::setSpeed(uint8_t speed) {
   _speed = speed;
   _driveMotor();
}

void Motor::drive(MotorCommand command, uint8_t speed) {
   _command = command;
   _speed = speed;
   _driveMotor();
}

void Motor::stop(MotorCommand command) {
   _command = (command == Brake) ? Brake : Coast;
   _driveMotor();
}

void Motor::_driveMotor() {
   if (_command == Brake || _command == Coast) _speed = 0;  // set speed to 0 if motor is stopping
   _speed = constrain(_speed, 0, 100);  // constrain speed to valid percentage range
   switch (_command) {
      case Forward:
         digitalWrite(_negPin, LOW);
         analogWrite(_posPin, map(_speed, 0, 100, 0, 255));  // use PWM to adjust speed
         break;
      case Reverse:
         digitalWrite(_posPin, LOW);
         analogWrite(_negPin, map(_speed, 0, 100, 0, 255));  // use PWM to adjust speed
         break;
      case Brake:
         digitalWrite(_posPin, HIGH);
         digitalWrite(_negPin, HIGH);
         break;
      default:  // Coast
         digitalWrite(_posPin, LOW);
         digitalWrite(_negPin, LOW);
         break;
   }
}

All of the public methods should be fairly easy to understand. They are essentially just retrieving or setting the motor’s various attributes. Many of them call the internal _driveMotor() method which is where the magic really happens. Within this method, we first make sure that the motor’s speed is set to an appropriate value and within a valid range. Then we send the appropriate signals to the motor driver’s input control pins (_posPin and _negPin) based on the motor’s current command and speed attributes.

The _posPin and _negPin pin pairs of the class correspond to the IN1 and IN2 pin pairs of the motor driver board. The board’s AIN1 / AIN2 and BIN1 / BIN2 input pin pairs control the motor driver’s AOUT1 / AOUT2 and BOUT1 / BOUT2 output pin pairs for the motors A and B respectively. The truth table below provides the basic operations.

IN1IN2OUT1OUT2Function
LowLowHigh ZHigh ZPassively coast to a stop
LowHighLowHighDrive backwards
HighLowHighLowDrive forward
HighHighLowLowActively brake to a stop

This control logic is typical for most H-bridge based motor drivers and is not specific to the DRV8833 chip and board.

We can also apply pulse width modulation (PWM) to the high side of the input pins to control the motor’s speed. This is accomplished by using the Arduino analogWrite() function where the PWM value (speed) is translated from 0-100% to the full range of PWM values (0-255) allowed on the GPIO pin.

Writing The Sketch

Now that our Motor class is written, let’s turn our attention to using it in a sketch. Open the Motors.ino tab and replace the code with that shown below.

#include "Motor.h"

#define DEBUG 1           // mode of operation; 0 = normal, 1 = debug
#define OP_DURATION 5000  // operation duration in milliseconds

const uint8_t DRV8833_SLP = 2;    // SLP (sleep) pin of DRV8833 board
const uint8_t DRV8833_AIN1 = 5;   // AIN1 (motor A control 1) pin of DRV8833 board
const uint8_t DRV8833_AIN2 = 6;   // AIN2 (motor A control 2) pin of DRV8833 board
const uint8_t DRV8833_BIN1 = 9;   // BIN1 (motor B control 1) pin of DRV8833 board
const uint8_t DRV8833_BIN2 = 10;  // BIN2 (motor B control 2) pin of DRV8833 board

Motor motorA = Motor(DRV8833_AIN1, DRV8833_AIN2);                 // motor A instance (uses default arguments)
Motor motorB = Motor(DRV8833_BIN1, DRV8833_BIN2, Coast, 0, "B");  // motor B instance (all arguments specified)

void setup() {
   // Serial Monitor
   if (DEBUG) {
      Serial.begin(9600);  // initialize serial bus
      while (!Serial);     // wait for serial connection
      Serial.println(F("Running in DEBUG mode.  Turn off for normal operation."));
   }

   // Pin configurations
   pinMode(DRV8833_SLP, OUTPUT);

   // Enable (turn on) motor driver
   digitalWrite(DRV8833_SLP, HIGH);
}

void loop() {
   basicOperations();  // perform basic motor control operations on motor A
}

void basicOperations() {
   // Basic operations with default attributes
   motorA.drive();                       // drive forward at full throttle
   if (DEBUG) printMotorStatus(motorA);
   delay(OP_DURATION);
   motorA.stop();                        // coast (soft) to a stop
   if (DEBUG) printMotorStatus(motorA);
   delay(OP_DURATION);

   // Basic operations with specific attributes
   motorA.drive(Forward, 75);            // drive forward at 75% throttle
   if (DEBUG) printMotorStatus(motorA);
   delay(OP_DURATION);
   motorA.stop(Coast);                   // coast (soft) to a stop
   if (DEBUG) printMotorStatus(motorA);
   delay(OP_DURATION);
   motorA.drive(Reverse, 50);            // drive in reverse at 50% throttle
   if (DEBUG) printMotorStatus(motorA);
   delay(OP_DURATION);
   motorA.stop(Brake);                   // brake (hard) to a stop
   if (DEBUG) printMotorStatus(motorA);
   delay(OP_DURATION);

   // Setting attributes
   motorA.setName("A");                  // change name of motor to A
   if (DEBUG) printMotorStatus(motorA);
   motorA.setCommand(Forward);           // drive forward at previously set speed
   if (DEBUG) printMotorStatus(motorA);
   delay(OP_DURATION);
   motorA.setSpeed(75);                  // set speed to 75% throttle with previously set command
   if (DEBUG) printMotorStatus(motorA);
   delay(OP_DURATION);
   motorA.setSpeed(0);                   // set speed to 0% throttle with previously set command
   if (DEBUG) printMotorStatus(motorA);
   delay(OP_DURATION);
   motorA.setCommand(Reverse);           // drive in reverse with previously set speed
   if (DEBUG) printMotorStatus(motorA);
   delay(OP_DURATION);
   motorA.setSpeed(75);                  // set speed to 75% throttle with previously set command
   if (DEBUG) printMotorStatus(motorA);
   delay(OP_DURATION);
   motorA.setCommand(Coast);             // coast (soft) to a stop
   if (DEBUG) printMotorStatus(motorA);
   delay(OP_DURATION);

   // Getting attributes
   // The command(), name(), and speed() methods retrieve the motor instance's
   // current command, name, and speed values respectively and are utilized
   // within the printMotorStatus() function used above.
}

void printMotorStatus(Motor motor) {
   const char* const command_states[] = {"Forward", "Reverse", "Brake", "Coast"};  // constant array of constant strings
   Serial.print(F("Motor "));
   Serial.print(motor.name());
   Serial.print(F(": Command = "));
   Serial.print(command_states[motor.command()]);
   Serial.print(F(", Speed = "));
   Serial.println(motor.speed());
}

Lines 6-10 define the pins of the Adafruit DRV8833 DC/Stepper Motor Driver Breakout Board connected to the Arduino.

Two instances of our Motor class, motorA and motorB, are defined on lines 12 and 13. motorA is created by specifying only the positive and negative motor control input pins leaving all other attributes set to their default values of command = Coast, speed = 0, name = "Unknown". motorB is created by specifying all the attributes.

The setup() function begins by initializing the serial bus for use with the Serial Monitor if the DEBUG flag is set to 1. The SLP pin of the motor driver board is pulled low by default which disables the motor driver itself. If you connected this pin to the Arduino, versus directly tying it high, then line 27 pulls the pin high to enable the driver for use.

Since we will be covering multiple examples of functionality, I separated them into their own distinct functions. Therefore, the basicOperations() function, this first example, is the only function I am currently calling within the loop() function. This example function covers the basic operations of controlling a motor with the Motor class.

We first look at the basic operations (methods) of the Motor class using default values. Beginning on line 36, motor A is driven forward at full throttle with the drive() method. We then print the status of the motor to the Serial Monitor and wait for a period of time. The delay time is determined by OP_DURATION (line 4), initially set to 5 seconds, which can be adjusted higher or lower to give you enough time to inspect the motor attributes in the Serial Monitor and see how the motor reacts to the command just given. We then proceed to stop the motor, using the default value of Coast in this case.

The next section, beginning in line 43, covers the same drive() and stop() methods, but this time specifying the parameters. The motor is driven at different speeds and is stopped using both the Coast and Brake commands.

Setting specific motor attributes (i.e. name, command, and speed) are covered in the next section beginning on line 57.

The last section, really just a comment, informs the user that the printMotorStatus() function can be viewed to show you how to retrieve the values of the various motor attributes. The command_states array is used within the printMotorStatus() function to print the human readable forms of the commands. Otherwise, you would just see the integer values as defined by the MotorCommand enumeration within the Motor class header file.

If there is something that needs further explanation, please let me know in the comment section and I will try to answer your question.

Save your program when you are done editing.

Testing The Design

Now that our circuit is built and our software is written, it is time to run and test our program.

Open the Serial Monitor window (Main Menu > Tools > Serial Monitor) so that we can see the program’s output. Upload (Main Menu > Sketch > Upload) the sketch to the board and you should see the motor start running through the basic operations while displaying the status of each operation within the Serial Monitor.

Notice how the name of the motor changes from “Unknown” to “A” partway through the operations. Also, watch how the motor responds to the various changes in direction, speed, and stopping commands.

We have covered all of the basic operations for controlling a motor with the Motor class and an attached motor driver board. You are now armed with the information you need to use them for your own ideas and projects. But I thought it would be nice to cover some additional functionality and offer you some more examples of how you could control the motor. Please continue reading if you are interested.

Adding Fault Checking

The DRV8833 motor driver chip used in the motor driver board provides a pin, named FLT, that is driven low when the chip detects a fault, e.g. thermal shutdown or overcurrent situations. When this happens, the chip shuts down the offending H-bridge but leaves the other one operational. Once the fault has cleared, the pin is driven high again. We can watch this pin to let us know if faults are occurring during our motor control operations.

Add the DRV8833 fault pin to the list of pin constants located towards the top of the Motors.ino sketch file.

const uint8_t DRV8833_FLT = 3;    // FLT (fault) pin of the DRV8833 board

Then add the following two lines just after the pin constants but before the motorA and motorB instance declarations.

volatile bool motorDriverFaultDetected = false;  // fault detected status
volatile bool motorDriverFaultCleared = false;   // fault cleared status

We will use an interrupt service routine (ISR) to check for changes in the FLT pin. This routine will set the motorDriverFaultDetected and motorDriverFaultCleared flags as those changes occur. The volatile keyword is used in the declarations to notify the compiler that the variables can be changed at any time by the ISR.

Next, add the ISR to the end of the sketch.

void motorDriverFaultPinChanged() {
   if (digitalRead(DRV8833_FLT) == LOW) motorDriverFaultDetected = true;
   else motorDriverFaultCleared = true;
}

In the setup() routine, add the following line to the pin configurations section to define the FLT pin as an input connected to the internal pull-up resistor of the microcontroller.

pinMode(DRV8833_FLT, INPUT_PULLUP);

After that line, but before enabling the motor driver, add the following to attach the ISR that will run when a change is detected on the FLT pin.

// Initialize interrupt service routine
// Calls motorDriverFaultPinChanged() if change detected on DRV8833_FLT pin
attachInterrupt(digitalPinToInterrupt(DRV8833_FLT), motorDriverFaultPinChanged, CHANGE);

Next, add the following to the top of the loop() function so that we can check for faults on each loop cycle

checkForMotorDriverFault();

and then add the function to the end of the sketch.

void checkForMotorDriverFault() {
   if (motorDriverFaultDetected) {
      if (DEBUG) Serial.println(F("Motor driver fault detected."));
      motorDriverFaultDetected = false;  // reset detected flag
   }
   if (motorDriverFaultCleared) {
      if (DEBUG) Serial.println(F("Motor driver fault cleared."));
      motorDriverFaultCleared = false;  // reset cleared flag
   }
}

This function just alerts the user if a fault was detected or cleared and resets the flags appropriately. Since the DRV8833 chip itself takes care of shutting down the offending circuity, checking for faults isn’t really necessary, but it is nice to know when it happens. Also, since this function is only run once during each loop pass, it will not show if multiple faults occurred within a single pass. For instance, if multiple faults happen to occur while running the basicOperations() example function, only one fault will be reported. If you want more granularity in your fault reporting, you could add a checkForMotorDriverFault() call after each operation.

Save your work and upload the updated sketch to test the code. It should function in the same manner as before, but this time, it will also let you know if any faults are occurring. When I run the sketch, I do see a fault happening on the first pass of the loop but not on subsequent passes; I don’t know why.

Ramping Up And Down The Speed

This example code shows how you can utilize the Motor class routines to ramp up and down the speed of a motor over a specific time frame. Add the following routines to the bottom of your sketch.

void rampUp(Motor motor, MotorCommand command, unsigned long duration) {
   if (command == Forward || command == Reverse) {
      motor.setCommand(command);
      for (uint8_t speed = 0; speed <= 100; speed++) {
         motor.setSpeed(speed);
         delay(duration/100);
      }
   }
}

void rampDown(Motor motor, MotorCommand command, unsigned long duration) {
   if (command == Forward || command == Reverse) {
      motor.setCommand(command);
      for (int8_t speed = 100; speed >= 0; speed--) {  // signed integer used to avoid rollover issues
         motor.setSpeed(speed);
         delay(duration/100);
      }
   }
}

Given the command, Forward or Reverse, and the length of time to take to ramp up (down) the speed from 0% to 100% (100% to 0%) throttle, these routines use a for loop to increase (decrease) the speed and pause for the appropriate amount of time between speed changes.

To utilize these functions, let’s create another example function, at the end of the sketch, that uses these functions

void rampingSpeed() {
   rampUp(motorA, Forward, OP_DURATION);    // ramp up forward speed for OP_DURATION ms
   rampDown(motorA, Forward, OP_DURATION);  // ramp down forward speed for OP_DURATION ms
}

and then add it to our loop() function. In addition, comment out the basicOperations() function so that it no longer runs.

void loop() {
   checkForMotorDriverFault();
   // basicOperations();  // perform basic motor control operations on motor A
   rampingSpeed();  // ramp up and down the speed of motor A
}

Save your work and upload the updated sketch to test the code. You should now see motor A ramp up and down continuously.

Using Variable Control

We haven’t used motor B yet, let’s fix that by incorporating it into this last example. Here we will utilize the potentiometer attached to the Arduino to manually adjust the speed of the motor.

Add the potentiometer to our list of pins towards the top of the sketch.

const uint8_t Pot = A0;           // pin connected to 10 KΩ potentiometer

Then add the potentiometerControl() example function to the bottom of the sketch.

void potentiometerControl() {
   static int previousReading = 0;
   int currentReading = analogRead(Pot);
   if (abs(currentReading - previousReading) > 10) {  // minimize unnecessary updates
      if (DEBUG) {
         Serial.print(F("Potentiometer reading: "));
         Serial.println(currentReading);
      }
      motorB.drive(Forward, map(currentReading, 0, 1023, 0, 100));  // maps ADC reading range to percentage range before driving
      if (DEBUG) printMotorStatus(motorB);
      previousReading = currentReading;
   }
}

This function reads the analog value of the potentiometer (line 3) and then maps that value, a range between 0 and 1023, to a range that is understood by our Motor class, 0 to 100, to drive the motor forward (line 9) at the user selected speed. Lines 2, 4, and 11 are used to limit constantly updating the motor by only changing the motor speed when the user actually adjusts the potentiometer and in increments understood by the Motor class (1023 / 100 ≈ 10).

If you are only using one motor in your design, remove the instantiation of motorB, towards the top of the sketch, and change the reference to motorB in the potentiometerControl() function to motorA.

Finally, add the example function to the loop() function.

void loop() {
   checkForMotorDriverFault();
   // basicOperations();  // perform basic motor control operations on motor A
   // rampingSpeed();  // ramp up and down the speed of motor A
   potentiometerControl();  // control motor B speed with a potentiometer
}

Save your work and upload the updated sketch. When you adjust the potentiometer, you should now see the speed of the motor change accordingly along with the potentiometer and motor values being displayed in the Serial Monitor. You will also notice that the motor stalls at slower speeds. The stall speed is dependent on the specifications of the attached motor along with the motor supply voltage being used.

Before we end, now would be a good time to upload the BareMinimum sketch (Main Menu > File > Examples > 01.Basics > BareMinimum) to reset all pins back to their default states. This ensures no outputs are being driven when plugging in your board for your next project.

Additional Resources

The following is a list of additional resources you may find helpful.

Summary

In this tutorial, we learned how to connect and control a small DC motor with an Arduino. We built a distinct Motor class that connects to a motor driver board and provides various methods for controlling the motor. We then utilized that class in our sketch to control a motor in a variety of ways, from performing basic operations to using a potentiometer to control the speed of the motor. We even added the ability to detect and report faults occurring on the motor driver board.

The final source code and schematic used for this tutorial are available on GitHub. The GitHub version of the code is fully commented to include additional information, such as the program’s description, circuit connections, code clarifications, and other details. The comments are also Doxygen compatible in case you want to generate the code documentation.

Thank you for joining me on this journey and I hope you enjoyed the experience. Please feel free to share your thoughts or questions in the comments section below.

This tutorial is provided as a free service to our valued readers. Please help us continue this endeavor by considering a donation.

About the author

John Woolsey

John is an electrical engineer who loves science, math, and technology and teaching it to others even more.
 
He knew he wanted to work with electronics from an early age, building his first robot when he was in 8th grade. His first computer was a Timex/Sinclair 2068 followed by the Tandy 1000 TL (aka really old stuff).
 
He put himself through college (The University of Texas at Austin) by working at Motorola where he worked for many years afterward in the Semiconductor Products Sector in Research and Development.
 
John started developing mobile app software in 2010 for himself and for other companies. He has also taught programming to kids for summer school and enjoyed years of judging kids science projects at the Austin Energy Regional Science Festival.
 
Electronics, software, and teaching all culminate in his new venture to learn, make, and teach others via the Woolsey Workshop website.

Leave a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.